Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: implement merging hooks provided in opts #5308

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/cyan-walls-remember.md
@@ -0,0 +1,5 @@
---
"@pnpm/pnpmfile": patch
---

Implement merging hooks provided in opts
72 changes: 52 additions & 20 deletions packages/pnpmfile/src/requireHooks.ts
Expand Up @@ -43,6 +43,7 @@ export default function requireHooks (
opts: {
globalPnpmfile?: string
pnpmfile?: string
hooks?: Hooks
}
): CookedHooks {
const globalPnpmfile = opts.globalPnpmfile && requirePnpmfile(pathAbsolute(opts.globalPnpmfile, prefix), prefix)
Expand All @@ -51,38 +52,69 @@ export default function requireHooks (
const pnpmFile = opts.pnpmfile && requirePnpmfile(pathAbsolute(opts.pnpmfile, prefix), prefix) ||
requirePnpmfile(path.join(prefix, '.pnpmfile.cjs'), prefix)
let hooks: Hooks = pnpmFile?.hooks
let optsHooks = opts?.hooks as Hooks

if (!globalHooks && !hooks) return {}
if (!globalHooks && !hooks && !optsHooks) return {}
globalHooks = globalHooks || {}
hooks = hooks || {}
optsHooks = optsHooks || {}
const cookedHooks: CookedHooks = {}
for (const hookName of ['readPackage', 'afterAllResolved']) {
if (globalHooks[hookName] && hooks[hookName]) {
// eslint-disable-next-line
const hookStack: Array<(arg: object) => Promise<any>> = []
if (globalHooks[hookName]) {
const globalHookContext = createReadPackageHookContext(globalPnpmfile.filename, prefix, hookName)
const localHookContext = createReadPackageHookContext(pnpmFile.filename, prefix, hookName)
// the `arg` is a package manifest in case of readPackage() and a lockfile object in case of afterAllResolved()
hookStack.push(async (arg: object) => {
return globalHooks[hookName](arg, globalHookContext)
})
}
if (hooks[hookName]) {
const localHookContext = createReadPackageHookContext(pnpmFile.filename, prefix, hookName)
hookStack.push(async (arg: object) => {
return hooks[hookName](arg, localHookContext)
})
}
if (optsHooks[hookName]) {
const optsHookContext = createReadPackageHookContext('opts', prefix, hookName)
hookStack.push(async (arg: object) => {
return optsHooks[hookName](arg, optsHookContext)
})
}
if (hookStack.length === 3) {
cookedHooks[hookName] = async (arg: object) => {
return hooks[hookName](
await globalHooks[hookName](arg, globalHookContext),
localHookContext
return hookStack[2](
await hookStack[1](
await hookStack[0](arg)
)
)
}
} else if (globalHooks[hookName]) {
const globalHook = globalHooks[hookName]
const context = createReadPackageHookContext(globalPnpmfile.filename, prefix, hookName)
cookedHooks[hookName] = (pkg: object) => globalHook(pkg, context)
} else if (hooks[hookName]) {
const hook = hooks[hookName]
const context = createReadPackageHookContext(pnpmFile.filename, prefix, hookName)
cookedHooks[hookName] = (pkg: object) => hook(pkg, context)
} else if (hookStack.length === 2) {
cookedHooks[hookName] = async (arg: object) => {
return hookStack[1](
await hookStack[0](arg)
)
}
} else if (hookStack.length === 1) {
cookedHooks[hookName] = hookStack[0]
}
}
const globalFilterLog = globalHooks.filterLog
const filterLog = hooks.filterLog
if (globalFilterLog != null && filterLog != null) {
cookedHooks.filterLog = (log: Log) => globalFilterLog(log) && filterLog(log)
} else {
cookedHooks.filterLog = globalFilterLog ?? filterLog
const filterLogStack: Array<(log: Log) => boolean> = []
if (globalHooks.filterLog) {
filterLogStack.push(globalHooks.filterLog)
}
if (hooks.filterLog) {
filterLogStack.push(hooks.filterLog)
}
if (optsHooks.filterLog) {
filterLogStack.push(optsHooks.filterLog)
}
if (filterLogStack.length === 3) {
cookedHooks.filterLog = (log: Log) => filterLogStack[0](log) && filterLogStack[1](log) && filterLogStack[2](log)
} if (filterLogStack.length === 2) {
cookedHooks.filterLog = (log: Log) => filterLogStack[0](log) && filterLogStack[1](log)
} if (filterLogStack.length === 1) {
cookedHooks.filterLog = filterLogStack[0]
}

// `importPackage`, `preResolution` and `fetchers` can only be defined via a global pnpmfile
Expand Down
103 changes: 103 additions & 0 deletions packages/pnpmfile/test/index.ts
Expand Up @@ -23,6 +23,29 @@ test('readPackage hook run fails when returned dependencies is not an object ',
).rejects.toEqual(new BadReadPackageHookError(pnpmfilePath, 'readPackage hook returned package manifest object\'s property \'dependencies\' must be an object.'))
})

test('filterLog hook works from single source', () => {
const pnpmfile = path.join(__dirname, 'pnpmfiles/filterLog.js')
const hooks = requireHooks(__dirname, { pnpmfile })

expect(hooks.filterLog).toBeDefined()
expect(hooks.filterLog!({
name: 'pnpm:summary',
level: 'error',
prefix: 'test',
})).toBeTruthy()
expect(hooks.filterLog!({
name: 'pnpm:summary',
level: 'warn',
prefix: 'test',
message: 'message',
})).toBeTruthy()
expect(hooks.filterLog!({
name: 'pnpm:summary',
level: 'debug',
prefix: 'test',
})).toBeTruthy()
})

test('filterLog hook combines with the global hook', () => {
const globalPnpmfile = path.join(__dirname, 'pnpmfiles/globalFilterLog.js')
const pnpmfile = path.join(__dirname, 'pnpmfiles/filterLog.js')
Expand All @@ -34,9 +57,89 @@ test('filterLog hook combines with the global hook', () => {
level: 'error',
prefix: 'test',
})).toBeTruthy()
expect(hooks.filterLog!({
name: 'pnpm:summary',
level: 'warn',
prefix: 'test',
message: 'message',
})).toBeTruthy()
expect(hooks.filterLog!({
name: 'pnpm:summary',
level: 'debug',
prefix: 'test',
})).toBeFalsy()
})

test('filterLog hook combines with the global hook and the options hook', () => {
const globalPnpmfile = path.join(__dirname, 'pnpmfiles/globalFilterLog.js')
const pnpmfile = path.join(__dirname, 'pnpmfiles/filterLog.js')
const hooks = requireHooks(__dirname, {
globalPnpmfile,
pnpmfile,
hooks: {
filterLog (log) {
return log.level === 'error'
},
},
})

expect(hooks.filterLog).toBeDefined()
expect(hooks.filterLog!({
name: 'pnpm:summary',
level: 'error',
prefix: 'test',
})).toBeTruthy()
expect(hooks.filterLog!({
name: 'pnpm:summary',
level: 'warn',
prefix: 'test',
message: 'message',
})).toBeFalsy()
expect(hooks.filterLog!({
name: 'pnpm:summary',
level: 'debug',
prefix: 'test',
})).toBeFalsy()
})

test('readPackage hook works from single source', async () => {
const pnpmfile = path.join(__dirname, 'pnpmfiles/readPackage.js')
const hooks = requireHooks(__dirname, { pnpmfile })

expect(hooks.readPackage).toBeDefined()
console.log(hooks.readPackage!({}))
expect(await hooks.readPackage!({})).toHaveProperty('local', true)
expect(await hooks.readPackage!({})).not.toHaveProperty('global', true)
expect(await hooks.readPackage!({})).not.toHaveProperty('opts', true)
})

test('readPackage hook combines with the global hook', async () => {
const pnpmfile = path.join(__dirname, 'pnpmfiles/readPackage.js')
const globalPnpmfile = path.join(__dirname, 'pnpmfiles/globalReadPackage.js')
const hooks = requireHooks(__dirname, { pnpmfile, globalPnpmfile })

expect(hooks.readPackage).toBeDefined()
expect(await hooks.readPackage!({})).toHaveProperty('local', true)
expect(await hooks.readPackage!({})).toHaveProperty('global', true)
expect(await hooks.readPackage!({})).not.toHaveProperty('opts', true)
})

test('readPackage hook combines with the global hook and the options hook', async () => {
const pnpmfile = path.join(__dirname, 'pnpmfiles/readPackage.js')
const globalPnpmfile = path.join(__dirname, 'pnpmfiles/globalReadPackage.js')
const hooks = requireHooks(__dirname, {
pnpmfile,
globalPnpmfile,
hooks: {
readPackage (pkg) {
pkg.opts = true
return pkg
},
},
})

expect(hooks.readPackage).toBeDefined()
expect(await hooks.readPackage!({})).toHaveProperty('local', true)
expect(await hooks.readPackage!({})).toHaveProperty('global', true)
expect(await hooks.readPackage!({})).toHaveProperty('opts', true)
})
2 changes: 1 addition & 1 deletion packages/pnpmfile/test/pnpmfiles/filterLog.js
Expand Up @@ -3,5 +3,5 @@ module.exports = {
}

function filterLog(log) {
return log.level === 'debug' || log.level === 'error'
return log.level === 'debug' || log.level === 'error' || log.level === 'warn'
}
2 changes: 1 addition & 1 deletion packages/pnpmfile/test/pnpmfiles/globalFilterLog.js
Expand Up @@ -3,5 +3,5 @@ module.exports = {
}

function filterLog(log) {
return log.level === 'error'
return log.level === 'error' || log.level === 'warn'
}
8 changes: 8 additions & 0 deletions packages/pnpmfile/test/pnpmfiles/globalReadPackage.js
@@ -0,0 +1,8 @@
module.exports = {
hooks: { readPackage }
}

function readPackage (pkg) {
pkg.global = true
return pkg
}
8 changes: 8 additions & 0 deletions packages/pnpmfile/test/pnpmfiles/readPackage.js
@@ -0,0 +1,8 @@
module.exports = {
hooks: { readPackage }
}

function readPackage (pkg) {
pkg.local = true
return pkg
}