mirror of
https://github.com/pnpm/pnpm.git
synced 2026-04-10 18:18:56 -04:00
feat: add new hook that runs pre resolution (#5143)
Co-authored-by: Zoltan Kochan <z@kochan.io>
This commit is contained in:
5
.changeset/nervous-poets-hunt.md
Normal file
5
.changeset/nervous-poets-hunt.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/lockfile-to-pnp": major
|
||||
---
|
||||
|
||||
Remove lockfileToPnp function.
|
||||
7
.changeset/red-readers-knock.md
Normal file
7
.changeset/red-readers-knock.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@pnpm/core": minor
|
||||
"pnpm": minor
|
||||
"@pnpm/pnpmfile": minor
|
||||
---
|
||||
|
||||
Add new `preResolution` hook.
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
17
packages/core/src/install/hooks.ts
Normal file
17
packages/core/src/install/hooks.ts
Normal 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>
|
||||
@@ -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
|
||||
|
||||
@@ -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:*",
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
"@pnpm/pnpmfile": "workspace:*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@pnpm/core": "workspace:*",
|
||||
"@pnpm/core-loggers": "workspace:*",
|
||||
"@pnpm/error": "workspace:*",
|
||||
"@pnpm/lockfile-types": "workspace:*",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
"../../typings/**/*.d.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../core"
|
||||
},
|
||||
{
|
||||
"path": "../core-loggers"
|
||||
},
|
||||
|
||||
9
pnpm-lock.yaml
generated
9
pnpm-lock.yaml
generated
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user