mirror of
https://github.com/pnpm/pnpm.git
synced 2025-12-24 07:38:12 -05:00
feat: allow to set multiple pnpmfiles (#9702)
This commit is contained in:
12
.changeset/full-trees-strive.md
Normal file
12
.changeset/full-trees-strive.md
Normal 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).
|
||||
5
.changeset/major-coats-make.md
Normal file
5
.changeset/major-coats-make.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/crypto.hash": minor
|
||||
---
|
||||
|
||||
A new function added: createHashFromMultipleFiles.
|
||||
5
.changeset/rich-sites-notice.md
Normal file
5
.changeset/rich-sites-notice.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/core": major
|
||||
---
|
||||
|
||||
`hooks.preResolution` is now an array of functions.
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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[]
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
1
deps/status/package.json
vendored
1
deps/status/package.json
vendored
@@ -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:*",
|
||||
|
||||
42
deps/status/src/checkDepsStatus.ts
vendored
42
deps/status/src/checkDepsStatus.ts
vendored
@@ -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
|
||||
}
|
||||
|
||||
87
deps/status/test/checkDepsStatus.test.ts
vendored
Normal file
87
deps/status/test/checkDepsStatus.test.ts
vendored
Normal 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')
|
||||
})
|
||||
})
|
||||
3
deps/status/tsconfig.json
vendored
3
deps/status/tsconfig.json
vendored
@@ -21,9 +21,6 @@
|
||||
{
|
||||
"path": "../../crypto/object-hasher"
|
||||
},
|
||||
{
|
||||
"path": "../../hooks/pnpmfile"
|
||||
},
|
||||
{
|
||||
"path": "../../lockfile/fs"
|
||||
},
|
||||
|
||||
@@ -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: [] } })
|
||||
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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')
|
||||
})
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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')
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -24,7 +24,7 @@ const DEFAULT_OPTIONS = {
|
||||
optionalDependencies: true,
|
||||
},
|
||||
lock: true,
|
||||
pnpmfile: '.pnpmfile.cjs',
|
||||
pnpmfile: ['.pnpmfile.cjs'],
|
||||
pnpmHomeDir: '',
|
||||
preferWorkspacePackages: true,
|
||||
rawConfig: { registry: REGISTRY_URL },
|
||||
|
||||
@@ -27,7 +27,7 @@ const DEFAULT_OPTIONS = {
|
||||
},
|
||||
lock: true,
|
||||
linkWorkspacePackages: true,
|
||||
pnpmfile: '.pnpmfile.cjs',
|
||||
pnpmfile: ['.pnpmfile.cjs'],
|
||||
pnpmHomeDir: '',
|
||||
preferWorkspacePackages: true,
|
||||
rawConfig: { registry: REGISTRY_URL },
|
||||
|
||||
@@ -31,7 +31,7 @@ const DEFAULT_OPTIONS = {
|
||||
optionalDependencies: true,
|
||||
},
|
||||
lock: true,
|
||||
pnpmfile: '.pnpmfile.cjs',
|
||||
pnpmfile: ['.pnpmfile.cjs'],
|
||||
pnpmHomeDir: '',
|
||||
preferWorkspacePackages: true,
|
||||
rawConfig: { registry: REGISTRY_URL },
|
||||
|
||||
@@ -28,7 +28,7 @@ const DEFAULT_OPTIONS = {
|
||||
optionalDependencies: true,
|
||||
},
|
||||
lock: true,
|
||||
pnpmfile: '.pnpmfile.cjs',
|
||||
pnpmfile: ['.pnpmfile.cjs'],
|
||||
pnpmHomeDir: '',
|
||||
preferWorkspacePackages: true,
|
||||
rawConfig: { registry: REGISTRY_URL },
|
||||
|
||||
@@ -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
3
pnpm-lock.yaml
generated
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 })
|
||||
|
||||
@@ -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' },
|
||||
|
||||
@@ -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' },
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -9,7 +9,7 @@ export interface WorkspaceState {
|
||||
name?: string
|
||||
version?: string
|
||||
}>
|
||||
pnpmfileExists: boolean
|
||||
pnpmfiles: string[]
|
||||
filteredInstall: boolean
|
||||
configDependencies?: Record<string, string>
|
||||
settings: WorkspaceStateSettings
|
||||
|
||||
@@ -9,7 +9,7 @@ export interface UpdateWorkspaceStateOptions {
|
||||
allProjects: ProjectsList
|
||||
settings: WorkspaceStateSettings
|
||||
workspaceDir: string
|
||||
pnpmfileExists: boolean
|
||||
pnpmfiles: string[]
|
||||
filteredInstall: boolean
|
||||
configDependencies?: Record<string, string>
|
||||
}
|
||||
|
||||
@@ -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: [],
|
||||
}))
|
||||
})
|
||||
|
||||
@@ -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'))
|
||||
)
|
||||
})
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user