feat: add new hook that runs pre resolution (#5143)

Co-authored-by: Zoltan Kochan <z@kochan.io>
This commit is contained in:
Dominic Elm
2022-08-08 13:14:16 +02:00
committed by GitHub
parent b67d3049b8
commit 5035fdae12
15 changed files with 123 additions and 48 deletions

View File

@@ -0,0 +1,5 @@
---
"@pnpm/lockfile-to-pnp": major
---
Remove lockfileToPnp function.

View File

@@ -0,0 +1,7 @@
---
"@pnpm/core": minor
"pnpm": minor
"@pnpm/pnpmfile": minor
---
Add new `preResolution` hook.

View File

@@ -8,6 +8,7 @@ export {
} from '@pnpm/types'
export { HoistingLimits } from '@pnpm/headless'
export * from './api'
export * from './install/hooks'
export { ProjectOptions, UnexpectedStoreError, UnexpectedVirtualStoreDirError } from '@pnpm/get-context'
export { InstallOptions } from './install/extendInstallOptions'

View File

@@ -15,6 +15,7 @@ import {
} from '@pnpm/types'
import pnpmPkgJson from '../pnpmPkgJson'
import { ReporterFunction } from '../types'
import { PreResolutionHookContext } from './hooks'
export interface StrictInstallOptions {
autoInstallPeers: boolean
@@ -70,6 +71,7 @@ export interface StrictInstallOptions {
pruneLockfileImporters: boolean
hooks: {
readPackage?: ReadPackageHook
preResolution?: (ctx: PreResolutionHookContext) => Promise<void>
afterAllResolved?: (lockfile: Lockfile) => Lockfile | Promise<Lockfile>
}
sideEffectsCacheRead: boolean

View File

@@ -0,0 +1,17 @@
import type { Lockfile } from '@pnpm/lockfile-file'
export interface PreResolutionHookContext {
wantedLockfile: Lockfile
currentLockfile: Lockfile
existsCurrentLockfile: boolean
existsWantedLockfile: boolean
lockfileDir: string
storeDir: string
}
export interface PreResolutionHookLogger {
info: (message: string) => void
warn: (message: string) => void
}
export type PreResolutioneHook = (ctx: PreResolutionHookContext, logger: PreResolutionHookLogger) => Promise<void>

View File

@@ -184,7 +184,20 @@ export async function mutateModules (
packageExtensions: opts.packageExtensions,
peerDependencyRules: opts.peerDependencyRules,
})
const ctx = await getContext(projects, opts)
if (opts.hooks.preResolution) {
await opts.hooks.preResolution({
currentLockfile: ctx.currentLockfile,
wantedLockfile: ctx.wantedLockfile,
existsCurrentLockfile: ctx.existsCurrentLockfile,
existsWantedLockfile: ctx.existsWantedLockfile,
lockfileDir: ctx.lockfileDir,
storeDir: ctx.storeDir,
})
}
const pruneVirtualStore = ctx.modulesFile?.prunedAt && opts.modulesCacheMaxAge > 0
? cacheExpired(ctx.modulesFile.prunedAt, opts.modulesCacheMaxAge)
: true

View File

@@ -41,10 +41,8 @@
"rimraf": "^3.0.2"
},
"dependencies": {
"@pnpm/config": "workspace:*",
"@pnpm/lockfile-file": "workspace:*",
"@pnpm/lockfile-utils": "workspace:*",
"@pnpm/read-project-manifest": "workspace:*",
"@pnpm/types": "workspace:*",
"@yarnpkg/pnp": "^2.3.2",
"dependency-path": "workspace:*",

View File

@@ -1,40 +1,14 @@
import { promises as fs } from 'fs'
import path from 'path'
import getConfigs from '@pnpm/config'
import { Lockfile, readWantedLockfile } from '@pnpm/lockfile-file'
import { Lockfile } from '@pnpm/lockfile-file'
import {
nameVerFromPkgSnapshot,
} from '@pnpm/lockfile-utils'
import readImporterManifest from '@pnpm/read-project-manifest'
import { Registries } from '@pnpm/types'
import { depPathToFilename, refToRelative } from 'dependency-path'
import { generateInlinedScript, PackageRegistry } from '@yarnpkg/pnp'
import normalizePath from 'normalize-path'
export async function lockfileToPnp (lockfileDir: string) {
const lockfile = await readWantedLockfile(lockfileDir, { ignoreIncompatible: true })
if (lockfile == null) throw new Error('Cannot generate a .pnp.cjs without a lockfile')
const importerNames: { [importerId: string]: string } = {}
await Promise.all(
Object.keys(lockfile.importers)
.map(async (importerId) => {
const importerDirectory = path.join(lockfileDir, importerId)
const { manifest } = await readImporterManifest(importerDirectory)
importerNames[importerId] = manifest.name as string
})
)
const { config: { registries, virtualStoreDir } } = await getConfigs({
cliOptions: {},
packageManager: { name: 'pnpm', version: '*' },
})
await writePnpFile(lockfile, {
importerNames,
lockfileDir,
registries,
virtualStoreDir: virtualStoreDir ?? path.join(lockfileDir, 'node_modules/.pnpm'),
})
}
export async function writePnpFile (
lockfile: Lockfile,
opts: {

View File

@@ -9,9 +9,6 @@
"../../typings/**/*.d.ts"
],
"references": [
{
"path": "../config"
},
{
"path": "../dependency-path"
},
@@ -21,9 +18,6 @@
{
"path": "../lockfile-utils"
},
{
"path": "../read-project-manifest"
},
{
"path": "../types"
}

View File

@@ -103,17 +103,27 @@ test('filterLog hook filters peer dependency warning', async () => {
test('importPackage hooks', async () => {
prepare()
const pnpmfile = `
const fs = require('fs')
const fs = require('fs')
module.exports = { hooks: { importPackage } }
module.exports = { hooks: { importPackage } }
function importPackage (to, opts) {
fs.writeFileSync('args.json', JSON.stringify([to, opts]), 'utf8')
return {}
}`
function importPackage (to, opts) {
fs.writeFileSync('args.json', JSON.stringify([to, opts]), 'utf8')
return {}
}
`
const npmrc = `
global-pnpmfile=.pnpmfile.cjs
`
await fs.writeFile('.npmrc', npmrc, 'utf8')
await fs.writeFile('.pnpmfile.cjs', pnpmfile, 'utf8')
await execPnpm(['add', 'is-positive@1.0.0'])
const [to, opts] = await loadJsonFile<any>('args.json') // eslint-disable-line
expect(typeof to).toBe('string')
expect(Object.keys(opts.filesMap).sort()).toStrictEqual([
'index.js',

View File

@@ -3,6 +3,7 @@ import path from 'path'
import { Lockfile } from '@pnpm/lockfile-types'
import prepare, { preparePackages } from '@pnpm/prepare'
import readYamlFile from 'read-yaml-file'
import loadJsonFile from 'load-json-file'
import writeYamlFile from 'write-yaml-file'
import {
addDistTag,
@@ -613,3 +614,33 @@ test('readPackage hook is used during removal inside a workspace', async () => {
const lockfile = await readYamlFile<Lockfile>('pnpm-lock.yaml')
expect(lockfile.packages!['/abc/1.0.0_vt2fli7reel7pfbmpdhs3d7fya'].peerDependencies!['is-negative']).toBe('1.0.0')
})
test('preResolution hook', async () => {
prepare()
const pnpmfile = `
const fs = require('fs')
module.exports = { hooks: { preResolution } }
function preResolution (ctx) {
fs.writeFileSync('args.json', JSON.stringify(ctx), 'utf8')
}
`
const npmrc = `
global-pnpmfile=.pnpmfile.cjs
`
await fs.writeFile('.npmrc', npmrc, 'utf8')
await fs.writeFile('.pnpmfile.cjs', pnpmfile, 'utf8')
await execPnpm(['add', 'is-positive@1.0.0'])
const ctx = await loadJsonFile<any>('args.json') // eslint-disable-line
expect(ctx.currentLockfile).toBeDefined()
expect(ctx.wantedLockfile).toBeDefined()
expect(ctx.lockfileDir).toBeDefined()
expect(ctx.storeDir).toBeDefined()
expect(ctx.existsCurrentLockfile).toBe(false)
expect(ctx.existsWantedLockfile).toBe(false)
})

View File

@@ -33,6 +33,7 @@
"@pnpm/pnpmfile": "workspace:*"
},
"dependencies": {
"@pnpm/core": "workspace:*",
"@pnpm/core-loggers": "workspace:*",
"@pnpm/error": "workspace:*",
"@pnpm/lockfile-types": "workspace:*",

View File

@@ -1,4 +1,5 @@
import path from 'path'
import type { PreResolutioneHook, PreResolutionHookContext, PreResolutionHookLogger } from '@pnpm/core'
import { hookLogger } from '@pnpm/core-loggers'
import pathAbsolute from 'path-absolute'
import type { Lockfile } from '@pnpm/lockfile-types'
@@ -13,6 +14,7 @@ interface HookContext {
interface Hooks {
// eslint-disable-next-line
readPackage?: (pkg: any, context: HookContext) => any
preResolution?: PreResolutioneHook
afterAllResolved?: (lockfile: Lockfile, context: HookContext) => Lockfile | Promise<Lockfile>
filterLog?: (log: Log) => boolean
importPackage?: ImportIndexedPackage
@@ -27,6 +29,7 @@ type Cook<T extends (...args: any[]) => any> = (
export interface CookedHooks {
readPackage?: Cook<Required<Hooks>['readPackage']>
preResolution?: Cook<Required<Hooks>['preResolution']>
afterAllResolved?: Cook<Required<Hooks>['afterAllResolved']>
filterLog?: Cook<Required<Hooks>['filterLog']>
importPackage?: ImportIndexedPackage
@@ -78,7 +81,17 @@ export default function requireHooks (
} else {
cookedHooks.filterLog = globalFilterLog ?? filterLog
}
cookedHooks.importPackage = hooks.importPackage ?? globalHooks.importPackage
// `importPackage` and `preResolution` can only be defined via a global pnpmfile
cookedHooks.importPackage = globalHooks.importPackage
const preResolutionHook = globalHooks.preResolution
cookedHooks.preResolution = preResolutionHook
? (ctx: PreResolutionHookContext) => preResolutionHook(ctx, createPreResolutionHookLogger(prefix))
: undefined
return cookedHooks
}
@@ -92,3 +105,12 @@ function createReadPackageHookContext (calledFrom: string, prefix: string, hook:
}),
}
}
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
}
}

View File

@@ -9,6 +9,9 @@
"../../typings/**/*.d.ts"
],
"references": [
{
"path": "../core"
},
{
"path": "../core-loggers"
},

9
pnpm-lock.yaml generated
View File

@@ -2005,18 +2005,12 @@ importers:
packages/lockfile-to-pnp:
dependencies:
'@pnpm/config':
specifier: workspace:*
version: link:../config
'@pnpm/lockfile-file':
specifier: workspace:*
version: link:../lockfile-file
'@pnpm/lockfile-utils':
specifier: workspace:*
version: link:../lockfile-utils
'@pnpm/read-project-manifest':
specifier: workspace:*
version: link:../read-project-manifest
'@pnpm/types':
specifier: workspace:*
version: link:../types
@@ -4356,6 +4350,9 @@ importers:
packages/pnpmfile:
dependencies:
'@pnpm/core':
specifier: workspace:*
version: link:../core
'@pnpm/core-loggers':
specifier: workspace:*
version: link:../core-loggers