mirror of
https://github.com/pnpm/pnpm.git
synced 2025-12-23 23:29:17 -05:00
feat(hooks): add beforePacking hook (#10303)
* feat(hooks): add `readPackageForPublishing` hook * feat: pass project `dir` parameter to `readPackageForPublishing` hook * chore: cleanup * fix: add support for multiple pnpmfiles * test: readPackageForPublishing hook * test: add more tests * test: small update * refactor: pass in `hooks` as an option * test: pass in `hooks` as an option * test: small update * chore: rename `readPackageForPublishing` to `beforePacking`
This commit is contained in:
6
.changeset/early-taxis-carry.md
Normal file
6
.changeset/early-taxis-carry.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
"@pnpm/exportable-manifest": minor
|
||||||
|
"pnpm": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Add support for a hook called `beforePacking` that can be used to customize the `package.json` contents at publish time [#3816](https://github.com/pnpm/pnpm/issues/3816).
|
||||||
@@ -6,6 +6,6 @@ charset = utf-8
|
|||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
|
|
||||||
[*.{ts,js,json}]
|
[*.{ts,js,cjs,json}]
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ export interface HookContext {
|
|||||||
export interface Hooks {
|
export interface Hooks {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Flexible hook signature for any package manifest
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Flexible hook signature for any package manifest
|
||||||
readPackage?: (pkg: any, context: HookContext) => any
|
readPackage?: (pkg: any, context: HookContext) => any
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Flexible hook signature for any package manifest
|
||||||
|
beforePacking?: (pkg: any, dir: string, context: HookContext) => any
|
||||||
preResolution?: PreResolutionHook
|
preResolution?: PreResolutionHook
|
||||||
afterAllResolved?: (lockfile: LockfileObject, context: HookContext) => LockfileObject | Promise<LockfileObject>
|
afterAllResolved?: (lockfile: LockfileObject, context: HookContext) => LockfileObject | Promise<LockfileObject>
|
||||||
filterLog?: (log: Log) => boolean
|
filterLog?: (log: Log) => boolean
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { createHashFromMultipleFiles } from '@pnpm/crypto.hash'
|
|||||||
import pathAbsolute from 'path-absolute'
|
import pathAbsolute from 'path-absolute'
|
||||||
import type { CustomFetchers } from '@pnpm/fetcher-base'
|
import type { CustomFetchers } from '@pnpm/fetcher-base'
|
||||||
import { type ImportIndexedPackageAsync } from '@pnpm/store-controller-types'
|
import { type ImportIndexedPackageAsync } from '@pnpm/store-controller-types'
|
||||||
import { type ReadPackageHook, type BaseManifest } from '@pnpm/types'
|
import { type ReadPackageHook, type BeforePackingHook, type BaseManifest } from '@pnpm/types'
|
||||||
import { type LockfileObject } from '@pnpm/lockfile.types'
|
import { type LockfileObject } from '@pnpm/lockfile.types'
|
||||||
import { requirePnpmfile, type Pnpmfile, type Finders } from './requirePnpmfile.js'
|
import { requirePnpmfile, type Pnpmfile, type Finders } from './requirePnpmfile.js'
|
||||||
import { type Hooks, type HookContext } from './Hooks.js'
|
import { type Hooks, type HookContext } from './Hooks.js'
|
||||||
@@ -34,6 +34,7 @@ interface PnpmfileEntryLoaded {
|
|||||||
|
|
||||||
export interface CookedHooks {
|
export interface CookedHooks {
|
||||||
readPackage?: ReadPackageHook[]
|
readPackage?: ReadPackageHook[]
|
||||||
|
beforePacking?: BeforePackingHook[]
|
||||||
preResolution?: Array<(ctx: PreResolutionHookContext) => Promise<void>>
|
preResolution?: Array<(ctx: PreResolutionHookContext) => Promise<void>>
|
||||||
afterAllResolved?: Array<(lockfile: LockfileObject) => LockfileObject | Promise<LockfileObject>>
|
afterAllResolved?: Array<(lockfile: LockfileObject) => LockfileObject | Promise<LockfileObject>>
|
||||||
filterLog?: Array<Cook<Required<Hooks>['filterLog']>>
|
filterLog?: Array<Cook<Required<Hooks>['filterLog']>>
|
||||||
@@ -104,8 +105,9 @@ export async function requireHooks (
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
const mergedFinders: Finders = {}
|
const mergedFinders: Finders = {}
|
||||||
const cookedHooks: CookedHooks & Required<Pick<CookedHooks, 'readPackage' | 'preResolution' | 'afterAllResolved' | 'filterLog' | 'updateConfig'>> = {
|
const cookedHooks: CookedHooks & Required<Pick<CookedHooks, 'readPackage' | 'beforePacking' | 'preResolution' | 'afterAllResolved' | 'filterLog' | 'updateConfig'>> = {
|
||||||
readPackage: [],
|
readPackage: [],
|
||||||
|
beforePacking: [],
|
||||||
preResolution: [],
|
preResolution: [],
|
||||||
afterAllResolved: [],
|
afterAllResolved: [],
|
||||||
filterLog: [],
|
filterLog: [],
|
||||||
@@ -154,6 +156,13 @@ export async function requireHooks (
|
|||||||
cookedHooks.readPackage.push(<Pkg extends BaseManifest>(pkg: Pkg, _dir?: string) => fn(pkg, context))
|
cookedHooks.readPackage.push(<Pkg extends BaseManifest>(pkg: Pkg, _dir?: string) => fn(pkg, context))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// beforePacking
|
||||||
|
if (fileHooks.beforePacking) {
|
||||||
|
const fn = fileHooks.beforePacking
|
||||||
|
const context = createReadPackageHookContext(file, prefix, 'beforePacking')
|
||||||
|
cookedHooks.beforePacking.push(<Pkg extends BaseManifest>(pkg: Pkg, dir: string) => fn(pkg, dir, context))
|
||||||
|
}
|
||||||
|
|
||||||
// afterAllResolved
|
// afterAllResolved
|
||||||
if (fileHooks.afterAllResolved) {
|
if (fileHooks.afterAllResolved) {
|
||||||
const fn = fileHooks.afterAllResolved
|
const fn = fileHooks.afterAllResolved
|
||||||
|
|||||||
@@ -82,6 +82,9 @@ export async function requirePnpmfile (pnpmFilePath: string, prefix: string): Pr
|
|||||||
}
|
}
|
||||||
return newPkg
|
return newPkg
|
||||||
}
|
}
|
||||||
|
if (pnpmfile?.hooks?.beforePacking && typeof pnpmfile.hooks.beforePacking !== 'function') {
|
||||||
|
throw new TypeError('hooks.beforePacking should be a function')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return { pnpmfileModule: pnpmfile }
|
return { pnpmfileModule: pnpmfile }
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ export type IncludedDependencies = {
|
|||||||
|
|
||||||
export type ReadPackageHook = <Pkg extends BaseManifest> (pkg: Pkg, dir?: string) => Pkg | Promise<Pkg>
|
export type ReadPackageHook = <Pkg extends BaseManifest> (pkg: Pkg, dir?: string) => Pkg | Promise<Pkg>
|
||||||
|
|
||||||
|
export type BeforePackingHook = <Pkg extends BaseManifest> (pkg: Pkg, dir: string) => Pkg | Promise<Pkg>
|
||||||
|
|
||||||
export interface FinderContext {
|
export interface FinderContext {
|
||||||
alias: string
|
alias: string
|
||||||
name: string
|
name: string
|
||||||
|
|||||||
@@ -43,6 +43,7 @@
|
|||||||
"@pnpm/catalogs.config": "workspace:*",
|
"@pnpm/catalogs.config": "workspace:*",
|
||||||
"@pnpm/catalogs.types": "workspace:*",
|
"@pnpm/catalogs.types": "workspace:*",
|
||||||
"@pnpm/exportable-manifest": "workspace:*",
|
"@pnpm/exportable-manifest": "workspace:*",
|
||||||
|
"@pnpm/pnpmfile": "workspace:*",
|
||||||
"@pnpm/prepare": "workspace:*",
|
"@pnpm/prepare": "workspace:*",
|
||||||
"@types/cross-spawn": "catalog:",
|
"@types/cross-spawn": "catalog:",
|
||||||
"@types/ramda": "catalog:",
|
"@types/ramda": "catalog:",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { type Catalogs } from '@pnpm/catalogs.types'
|
|||||||
import { PnpmError } from '@pnpm/error'
|
import { PnpmError } from '@pnpm/error'
|
||||||
import { parseJsrSpecifier } from '@pnpm/resolving.jsr-specifier-parser'
|
import { parseJsrSpecifier } from '@pnpm/resolving.jsr-specifier-parser'
|
||||||
import { tryReadProjectManifest } from '@pnpm/read-project-manifest'
|
import { tryReadProjectManifest } from '@pnpm/read-project-manifest'
|
||||||
|
import { type Hooks } from '@pnpm/pnpmfile'
|
||||||
import { type Dependencies, type ProjectManifest } from '@pnpm/types'
|
import { type Dependencies, type ProjectManifest } from '@pnpm/types'
|
||||||
import { omit } from 'ramda'
|
import { omit } from 'ramda'
|
||||||
import pMapValues from 'p-map-values'
|
import pMapValues from 'p-map-values'
|
||||||
@@ -20,6 +21,7 @@ const PREPUBLISH_SCRIPTS = [
|
|||||||
|
|
||||||
export interface MakePublishManifestOptions {
|
export interface MakePublishManifestOptions {
|
||||||
catalogs: Catalogs
|
catalogs: Catalogs
|
||||||
|
hooks?: Hooks
|
||||||
modulesDir?: string
|
modulesDir?: string
|
||||||
readmeFile?: string
|
readmeFile?: string
|
||||||
}
|
}
|
||||||
@@ -29,7 +31,7 @@ export async function createExportableManifest (
|
|||||||
originalManifest: ProjectManifest,
|
originalManifest: ProjectManifest,
|
||||||
opts: MakePublishManifestOptions
|
opts: MakePublishManifestOptions
|
||||||
): Promise<ProjectManifest> {
|
): Promise<ProjectManifest> {
|
||||||
const publishManifest: ProjectManifest = omit(['pnpm', 'scripts', 'packageManager'], originalManifest)
|
let publishManifest: ProjectManifest = omit(['pnpm', 'scripts', 'packageManager'], originalManifest)
|
||||||
if (originalManifest.scripts != null) {
|
if (originalManifest.scripts != null) {
|
||||||
publishManifest.scripts = omit(PREPUBLISH_SCRIPTS, originalManifest.scripts)
|
publishManifest.scripts = omit(PREPUBLISH_SCRIPTS, originalManifest.scripts)
|
||||||
}
|
}
|
||||||
@@ -63,6 +65,11 @@ export async function createExportableManifest (
|
|||||||
publishManifest.readme ??= opts.readmeFile
|
publishManifest.readme ??= opts.readmeFile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const hook of opts?.hooks?.beforePacking ?? []) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
publishManifest = await hook(publishManifest, dir) ?? publishManifest
|
||||||
|
}
|
||||||
|
|
||||||
return publishManifest
|
return publishManifest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
101
pkg-manifest/exportable-manifest/test/beforePackingHook.test.ts
Normal file
101
pkg-manifest/exportable-manifest/test/beforePackingHook.test.ts
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import fs from 'fs'
|
||||||
|
import { createExportableManifest, type MakePublishManifestOptions } from '@pnpm/exportable-manifest'
|
||||||
|
import { requireHooks } from '@pnpm/pnpmfile'
|
||||||
|
import { prepare } from '@pnpm/prepare'
|
||||||
|
import { sync as writeYamlFile } from 'write-yaml-file'
|
||||||
|
|
||||||
|
const defaultOpts: MakePublishManifestOptions = {
|
||||||
|
catalogs: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
test('basic test', async () => {
|
||||||
|
prepare()
|
||||||
|
|
||||||
|
fs.writeFileSync('.pnpmfile.cjs', `
|
||||||
|
module.exports = {
|
||||||
|
hooks: {
|
||||||
|
beforePacking: (pkg, dir, context) => {
|
||||||
|
context.log(dir)
|
||||||
|
pkg.foo = 'bar'
|
||||||
|
return pkg // return optional
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}`, 'utf8')
|
||||||
|
|
||||||
|
const { hooks } = await requireHooks(process.cwd(), { tryLoadDefaultPnpmfile: true })
|
||||||
|
expect(await createExportableManifest(process.cwd(), {
|
||||||
|
name: 'foo',
|
||||||
|
version: '1.0.0',
|
||||||
|
dependencies: {
|
||||||
|
qar: '2',
|
||||||
|
},
|
||||||
|
}, { ...defaultOpts, hooks })).toStrictEqual({
|
||||||
|
name: 'foo',
|
||||||
|
version: '1.0.0',
|
||||||
|
dependencies: {
|
||||||
|
qar: '2',
|
||||||
|
},
|
||||||
|
foo: 'bar',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('hook returns new manifest', async () => {
|
||||||
|
prepare()
|
||||||
|
|
||||||
|
fs.writeFileSync('.pnpmfile.cjs', `
|
||||||
|
module.exports = {
|
||||||
|
hooks: {
|
||||||
|
beforePacking: (pkg) => {
|
||||||
|
return { type: 'module' }
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}`, 'utf8')
|
||||||
|
|
||||||
|
const { hooks } = await requireHooks(process.cwd(), { tryLoadDefaultPnpmfile: true })
|
||||||
|
expect(await createExportableManifest(process.cwd(), {
|
||||||
|
name: 'foo',
|
||||||
|
version: '1.0.0',
|
||||||
|
}, { ...defaultOpts, hooks })).toStrictEqual({
|
||||||
|
type: 'module',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('hook in multiple pnpmfiles', async () => {
|
||||||
|
prepare()
|
||||||
|
|
||||||
|
const pnpmfiles = ['pnpmfile1.cjs', 'pnpmfile2.cjs']
|
||||||
|
fs.writeFileSync(pnpmfiles[0], `
|
||||||
|
module.exports = {
|
||||||
|
hooks: {
|
||||||
|
beforePacking: (pkg) => {
|
||||||
|
pkg.foo = 'foo'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}`, 'utf8')
|
||||||
|
fs.writeFileSync(pnpmfiles[1], `
|
||||||
|
module.exports = {
|
||||||
|
hooks: {
|
||||||
|
beforePacking: (pkg) => {
|
||||||
|
pkg.bar = 'bar'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}`, 'utf8')
|
||||||
|
writeYamlFile('pnpm-workspace.yaml', { pnpmfile: pnpmfiles })
|
||||||
|
|
||||||
|
const { hooks } = await requireHooks(process.cwd(), { pnpmfiles })
|
||||||
|
expect(await createExportableManifest(process.cwd(), {
|
||||||
|
name: 'foo',
|
||||||
|
version: '1.0.0',
|
||||||
|
dependencies: {
|
||||||
|
qar: '2',
|
||||||
|
},
|
||||||
|
}, { ...defaultOpts, hooks })).toStrictEqual({
|
||||||
|
name: 'foo',
|
||||||
|
version: '1.0.0',
|
||||||
|
dependencies: {
|
||||||
|
qar: '2',
|
||||||
|
},
|
||||||
|
foo: 'foo',
|
||||||
|
bar: 'bar',
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -21,6 +21,9 @@
|
|||||||
{
|
{
|
||||||
"path": "../../catalogs/types"
|
"path": "../../catalogs/types"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "../../hooks/pnpmfile"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "../../packages/error"
|
"path": "../../packages/error"
|
||||||
},
|
},
|
||||||
|
|||||||
6
pnpm-lock.yaml
generated
6
pnpm-lock.yaml
generated
@@ -6381,6 +6381,9 @@ importers:
|
|||||||
'@pnpm/exportable-manifest':
|
'@pnpm/exportable-manifest':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: 'link:'
|
version: 'link:'
|
||||||
|
'@pnpm/pnpmfile':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../hooks/pnpmfile
|
||||||
'@pnpm/prepare':
|
'@pnpm/prepare':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../__utils__/prepare
|
version: link:../../__utils__/prepare
|
||||||
@@ -7155,6 +7158,9 @@ importers:
|
|||||||
'@pnpm/plugin-commands-publishing':
|
'@pnpm/plugin-commands-publishing':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: 'link:'
|
version: 'link:'
|
||||||
|
'@pnpm/pnpmfile':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../hooks/pnpmfile
|
||||||
'@pnpm/prepare':
|
'@pnpm/prepare':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../__utils__/prepare
|
version: link:../../__utils__/prepare
|
||||||
|
|||||||
@@ -75,6 +75,7 @@
|
|||||||
"@pnpm/catalogs.config": "workspace:*",
|
"@pnpm/catalogs.config": "workspace:*",
|
||||||
"@pnpm/logger": "workspace:*",
|
"@pnpm/logger": "workspace:*",
|
||||||
"@pnpm/plugin-commands-publishing": "workspace:*",
|
"@pnpm/plugin-commands-publishing": "workspace:*",
|
||||||
|
"@pnpm/pnpmfile": "workspace:*",
|
||||||
"@pnpm/prepare": "workspace:*",
|
"@pnpm/prepare": "workspace:*",
|
||||||
"@pnpm/registry-mock": "catalog:",
|
"@pnpm/registry-mock": "catalog:",
|
||||||
"@pnpm/test-ipc-server": "workspace:*",
|
"@pnpm/test-ipc-server": "workspace:*",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { readProjectManifest } from '@pnpm/cli-utils'
|
|||||||
import { createExportableManifest } from '@pnpm/exportable-manifest'
|
import { createExportableManifest } from '@pnpm/exportable-manifest'
|
||||||
import { packlist } from '@pnpm/fs.packlist'
|
import { packlist } from '@pnpm/fs.packlist'
|
||||||
import { getBinsFromPackageManifest } from '@pnpm/package-bins'
|
import { getBinsFromPackageManifest } from '@pnpm/package-bins'
|
||||||
|
import { type Hooks } from '@pnpm/pnpmfile'
|
||||||
import { type ProjectManifest, type Project, type ProjectRootDir, type ProjectsGraph, type DependencyManifest } from '@pnpm/types'
|
import { type ProjectManifest, type Project, type ProjectRootDir, type ProjectsGraph, type DependencyManifest } from '@pnpm/types'
|
||||||
import { glob } from 'tinyglobby'
|
import { glob } from 'tinyglobby'
|
||||||
import { pick } from 'ramda'
|
import { pick } from 'ramda'
|
||||||
@@ -98,6 +99,7 @@ export type PackOptions = Pick<UniversalOptions, 'dir'> & Pick<Config, 'catalogs
|
|||||||
| 'nodeLinker'
|
| 'nodeLinker'
|
||||||
> & Partial<Pick<Config, 'extraBinPaths'
|
> & Partial<Pick<Config, 'extraBinPaths'
|
||||||
| 'extraEnv'
|
| 'extraEnv'
|
||||||
|
| 'hooks'
|
||||||
| 'recursive'
|
| 'recursive'
|
||||||
| 'selectedProjectsGraph'
|
| 'selectedProjectsGraph'
|
||||||
| 'workspaceConcurrency'
|
| 'workspaceConcurrency'
|
||||||
@@ -239,6 +241,7 @@ export async function api (opts: PackOptions): Promise<PackResult> {
|
|||||||
manifest,
|
manifest,
|
||||||
embedReadme: opts.embedReadme,
|
embedReadme: opts.embedReadme,
|
||||||
catalogs: opts.catalogs ?? {},
|
catalogs: opts.catalogs ?? {},
|
||||||
|
hooks: opts.hooks,
|
||||||
})
|
})
|
||||||
const files = await packlist(dir, {
|
const files = await packlist(dir, {
|
||||||
packageJsonCache: {
|
packageJsonCache: {
|
||||||
@@ -355,11 +358,13 @@ async function createPublishManifest (opts: {
|
|||||||
modulesDir: string
|
modulesDir: string
|
||||||
manifest: ProjectManifest
|
manifest: ProjectManifest
|
||||||
catalogs: Catalogs
|
catalogs: Catalogs
|
||||||
|
hooks?: Hooks
|
||||||
}): Promise<ProjectManifest> {
|
}): Promise<ProjectManifest> {
|
||||||
const { projectDir, embedReadme, modulesDir, manifest, catalogs } = opts
|
const { projectDir, embedReadme, modulesDir, manifest, catalogs, hooks } = opts
|
||||||
const readmeFile = embedReadme ? await readReadmeFile(projectDir) : undefined
|
const readmeFile = embedReadme ? await readReadmeFile(projectDir) : undefined
|
||||||
return createExportableManifest(projectDir, manifest, {
|
return createExportableManifest(projectDir, manifest, {
|
||||||
catalogs,
|
catalogs,
|
||||||
|
hooks,
|
||||||
readmeFile,
|
readmeFile,
|
||||||
modulesDir,
|
modulesDir,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -45,6 +45,9 @@
|
|||||||
{
|
{
|
||||||
"path": "../../fs/packlist"
|
"path": "../../fs/packlist"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "../../hooks/pnpmfile"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "../../network/auth-header"
|
"path": "../../network/auth-header"
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user