mirror of
https://github.com/pnpm/pnpm.git
synced 2025-12-23 23:29:17 -05:00
23
.changeset/bright-lemons-arrive.md
Normal file
23
.changeset/bright-lemons-arrive.md
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
"@pnpm/core": minor
|
||||
"@pnpm/headless": minor
|
||||
"@pnpm/types": minor
|
||||
"pnpm": minor
|
||||
"@pnpm/resolve-dependencies": minor
|
||||
"@pnpm/lockfile-types": minor
|
||||
"@pnpm/lifecycle": minor
|
||||
"@pnpm/plugin-commands-installation": minor
|
||||
---
|
||||
|
||||
Dependencies patching is possible via the `pnpm.patchedDependencies` field of the `package.json`.
|
||||
To patch a package, the package name, exact version, and the relative path to the patch file should be specified. For instance:
|
||||
|
||||
```json
|
||||
{
|
||||
"pnpm": {
|
||||
"patchedDependencies": {
|
||||
"eslint@1.0.0": "./patches/eslint@1.0.0.patch"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
5
.changeset/four-poems-leave.md
Normal file
5
.changeset/four-poems-leave.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/calc-dep-state": major
|
||||
---
|
||||
|
||||
Changed the order of arguments in calcDepState and added an optional last argument for patchFileHash.
|
||||
9
.changeset/shiny-countries-behave.md
Normal file
9
.changeset/shiny-countries-behave.md
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
"@pnpm/create-cafs-store": major
|
||||
"@pnpm/fetcher-base": major
|
||||
"@pnpm/package-store": major
|
||||
"@pnpm/server": major
|
||||
"@pnpm/store-controller-types": major
|
||||
---
|
||||
|
||||
Rename engine and targetEngine fields to sideEffectsCacheKey.
|
||||
5
.changeset/tasty-cobras-reflect.md
Normal file
5
.changeset/tasty-cobras-reflect.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/build-modules": minor
|
||||
---
|
||||
|
||||
Support packages patching.
|
||||
@@ -16,6 +16,10 @@ export interface DependenciesGraphNode {
|
||||
optionalDependencies: Set<string>
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
requiresBuild?: boolean | any // this is a durty workaround added in https://github.com/pnpm/pnpm/pull/4898
|
||||
patchFile?: {
|
||||
hash: string
|
||||
path: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface DependenciesGraph {
|
||||
|
||||
@@ -83,6 +83,7 @@ async function buildDependency (
|
||||
extraEnv: opts.extraEnv,
|
||||
initCwd: opts.lockfileDir,
|
||||
optional: depNode.optional,
|
||||
patchPath: depNode.patchFile?.path,
|
||||
pkgRoot: depNode.dir,
|
||||
rawConfig: opts.rawConfig,
|
||||
rootModulesDir: opts.rootModulesDir,
|
||||
@@ -93,8 +94,9 @@ async function buildDependency (
|
||||
})
|
||||
if (hasSideEffects && opts.sideEffectsCacheWrite) {
|
||||
try {
|
||||
const sideEffectsCacheKey = calcDepState(depGraph, opts.depsStateCache, depPath, depNode.patchFile?.hash)
|
||||
await opts.storeController.upload(depNode.dir, {
|
||||
engine: calcDepState(depPath, depGraph, opts.depsStateCache),
|
||||
sideEffectsCacheKey,
|
||||
filesIndexFile: depNode.filesIndexFile,
|
||||
})
|
||||
} catch (err: any) { // eslint-disable-line
|
||||
|
||||
@@ -11,7 +11,7 @@ export interface DepsGraphNode {
|
||||
}
|
||||
|
||||
export interface DepsStateCache {
|
||||
[nodeId: string]: DepStateObj
|
||||
[depPath: string]: DepStateObj
|
||||
}
|
||||
|
||||
export interface DepStateObj {
|
||||
@@ -19,22 +19,27 @@ export interface DepStateObj {
|
||||
}
|
||||
|
||||
export function calcDepState (
|
||||
nodeId: string,
|
||||
depsGraph: DepsGraph,
|
||||
cache: DepsStateCache
|
||||
cache: DepsStateCache,
|
||||
depPath: string,
|
||||
patchFileHash?: string
|
||||
): string {
|
||||
const depStateObj = calcDepStateObj(nodeId, depsGraph, cache, new Set())
|
||||
return `${ENGINE_NAME}-${JSON.stringify(depStateObj)}`
|
||||
const depStateObj = calcDepStateObj(depPath, depsGraph, cache, new Set())
|
||||
let result = `${ENGINE_NAME}-${JSON.stringify(depStateObj)}`
|
||||
if (patchFileHash) {
|
||||
result += `-${patchFileHash}`
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function calcDepStateObj (
|
||||
nodeId: string,
|
||||
depPath: string,
|
||||
depsGraph: DepsGraph,
|
||||
cache: DepsStateCache,
|
||||
parents: Set<string>
|
||||
): DepStateObj {
|
||||
if (cache[nodeId]) return cache[nodeId]
|
||||
const node = depsGraph[nodeId]
|
||||
if (cache[depPath]) return cache[depPath]
|
||||
const node = depsGraph[depPath]
|
||||
if (!node) return {}
|
||||
const nextParents = new Set([...Array.from(parents), node.depPath])
|
||||
const state: DepStateObj = {}
|
||||
@@ -47,6 +52,6 @@ function calcDepStateObj (
|
||||
}
|
||||
state[child.depPath] = calcDepStateObj(childId, depsGraph, cache, nextParents)
|
||||
}
|
||||
cache[nodeId] = sortKeys(state)
|
||||
return cache[nodeId]
|
||||
cache[depPath] = sortKeys(state)
|
||||
return cache[depPath]
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { calcDepState } from '@pnpm/calc-dep-state'
|
||||
import { ENGINE_NAME } from '@pnpm/constants'
|
||||
|
||||
test('calcDepState()', () => {
|
||||
expect(calcDepState('/registry/foo/1.0.0', {
|
||||
expect(calcDepState({
|
||||
'registry/foo/1.0.0': {
|
||||
depPath: '/foo/1.0.0',
|
||||
children: {
|
||||
@@ -15,5 +15,5 @@ test('calcDepState()', () => {
|
||||
foo: 'registry/foo/1.0.0',
|
||||
},
|
||||
},
|
||||
}, {})).toBe(`${ENGINE_NAME}-{}`)
|
||||
}, {}, '/registry/foo/1.0.0')).toBe(`${ENGINE_NAME}-{}`)
|
||||
})
|
||||
|
||||
@@ -100,6 +100,7 @@ export interface StrictInstallOptions {
|
||||
|
||||
global: boolean
|
||||
globalBin?: string
|
||||
patchedDependencies?: Record<string, string>
|
||||
}
|
||||
|
||||
export type InstallOptions =
|
||||
|
||||
@@ -240,6 +240,7 @@ export async function mutateModules (
|
||||
neverBuiltDependencies: opts.neverBuiltDependencies,
|
||||
onlyBuiltDependencies: opts.onlyBuiltDependencies,
|
||||
packageExtensionsChecksum,
|
||||
patchedDependencies: opts.patchedDependencies,
|
||||
}) ||
|
||||
opts.fixLockfile
|
||||
if (needsFullResolution) {
|
||||
@@ -247,6 +248,7 @@ export async function mutateModules (
|
||||
ctx.wantedLockfile.neverBuiltDependencies = opts.neverBuiltDependencies
|
||||
ctx.wantedLockfile.onlyBuiltDependencies = opts.onlyBuiltDependencies
|
||||
ctx.wantedLockfile.packageExtensionsChecksum = packageExtensionsChecksum
|
||||
ctx.wantedLockfile.patchedDependencies = opts.patchedDependencies
|
||||
}
|
||||
const frozenLockfile = opts.frozenLockfile ||
|
||||
opts.frozenLockfileIfExists && ctx.existsWantedLockfile
|
||||
@@ -490,16 +492,19 @@ function lockfileIsUpToDate (
|
||||
onlyBuiltDependencies,
|
||||
overrides,
|
||||
packageExtensionsChecksum,
|
||||
patchedDependencies,
|
||||
}: {
|
||||
neverBuiltDependencies?: string[]
|
||||
onlyBuiltDependencies?: string[]
|
||||
overrides?: Record<string, string>
|
||||
packageExtensionsChecksum?: string
|
||||
patchedDependencies?: Record<string, string>
|
||||
}) {
|
||||
return !equals(lockfile.overrides ?? {}, overrides ?? {}) ||
|
||||
!equals((lockfile.neverBuiltDependencies ?? []).sort(), (neverBuiltDependencies ?? []).sort()) ||
|
||||
!equals(onlyBuiltDependencies?.sort(), lockfile.onlyBuiltDependencies) ||
|
||||
lockfile.packageExtensionsChecksum !== packageExtensionsChecksum
|
||||
lockfile.packageExtensionsChecksum !== packageExtensionsChecksum ||
|
||||
!equals(lockfile.patchedDependencies ?? {}, patchedDependencies ?? {})
|
||||
}
|
||||
|
||||
export function createObjectChecksum (obj: Object) {
|
||||
@@ -773,6 +778,7 @@ const _installInContext: InstallFunction = async (projects, ctx, opts) => {
|
||||
virtualStoreDir: ctx.virtualStoreDir,
|
||||
wantedLockfile: ctx.wantedLockfile,
|
||||
workspacePackages: opts.workspacePackages,
|
||||
patchedDependencies: opts.patchedDependencies,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -404,9 +404,9 @@ async function linkAllPkgs (
|
||||
depNodes.map(async (depNode) => {
|
||||
const filesResponse = await depNode.fetchingFiles()
|
||||
|
||||
let targetEngine: string | undefined
|
||||
let sideEffectsCacheKey: string | undefined
|
||||
if (opts.sideEffectsCacheRead && filesResponse.sideEffects && !isEmpty(filesResponse.sideEffects)) {
|
||||
targetEngine = calcDepState(depNode.depPath, opts.depGraph, opts.depsStateCache)
|
||||
sideEffectsCacheKey = calcDepState(opts.depGraph, opts.depsStateCache, depNode.depPath, depNode.patchFile?.hash)
|
||||
}
|
||||
if (typeof depNode.requiresBuild === 'function') {
|
||||
depNode.requiresBuild = await depNode.requiresBuild()
|
||||
@@ -414,7 +414,7 @@ async function linkAllPkgs (
|
||||
const { importMethod, isBuilt } = await storeController.importPackage(depNode.dir, {
|
||||
filesResponse,
|
||||
force: opts.force,
|
||||
targetEngine,
|
||||
sideEffectsCacheKey,
|
||||
requiresBuild: depNode.requiresBuild,
|
||||
})
|
||||
if (importMethod) {
|
||||
|
||||
12
packages/core/test/fixtures/patch-pkg/is-positive@1.0.0.patch
vendored
Normal file
12
packages/core/test/fixtures/patch-pkg/is-positive@1.0.0.patch
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
diff --git a/index.js b/index.js
|
||||
index 8e020ca..ff3aee4 100644
|
||||
--- a/index.js
|
||||
+++ b/index.js
|
||||
@@ -5,5 +5,6 @@ module.exports = function (n) {
|
||||
throw new TypeError('Expected a number');
|
||||
}
|
||||
|
||||
+ // patched
|
||||
return n >= 0;
|
||||
};
|
||||
|
||||
90
packages/core/test/install/patch.ts
Normal file
90
packages/core/test/install/patch.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { PackageFilesIndex } from '@pnpm/cafs'
|
||||
import { ENGINE_NAME } from '@pnpm/constants'
|
||||
import { install } from '@pnpm/core'
|
||||
import { prepareEmpty } from '@pnpm/prepare'
|
||||
import fixtures from '@pnpm/test-fixtures'
|
||||
import rimraf from '@zkochan/rimraf'
|
||||
import loadJsonFile from 'load-json-file'
|
||||
import { testDefaults } from '../utils'
|
||||
|
||||
const f = fixtures(__dirname)
|
||||
|
||||
test('patch package', async () => {
|
||||
const project = prepareEmpty()
|
||||
const patchPath = path.join(f.find('patch-pkg'), 'is-positive@1.0.0.patch')
|
||||
|
||||
const patchedDependencies = {
|
||||
'is-positive@1.0.0': path.relative(process.cwd(), patchPath),
|
||||
}
|
||||
const opts = await testDefaults({
|
||||
fastUnpack: false,
|
||||
sideEffectsCacheRead: true,
|
||||
sideEffectsCacheWrite: true,
|
||||
patchedDependencies,
|
||||
}, {}, {}, { packageImportMethod: 'hardlink' })
|
||||
await install({
|
||||
dependencies: {
|
||||
'is-positive': '1.0.0',
|
||||
},
|
||||
}, opts)
|
||||
|
||||
expect(fs.readFileSync('node_modules/is-positive/index.js', 'utf8')).toContain('// patched')
|
||||
|
||||
const lockfile = await project.readLockfile()
|
||||
expect(lockfile.patchedDependencies).toStrictEqual(patchedDependencies)
|
||||
|
||||
const filesIndexFile = path.join(opts.storeDir, 'files/c7/1ccf199e0fdae37aad13946b937d67bcd35fa111b84d21b3a19439cfdc2812c5d8da8a735e94c2a1ccb77b4583808ee8405313951e7146ac83ede3671dc292-index.json')
|
||||
const filesIndex = await loadJsonFile<PackageFilesIndex>(filesIndexFile)
|
||||
const sideEffectsKey = `${ENGINE_NAME}-{}-meyqmf5tej4bwn3gxydpfig6pe`
|
||||
const patchedFileIntegrity = filesIndex.sideEffects?.[sideEffectsKey]['index.js']?.integrity
|
||||
expect(patchedFileIntegrity).toBeTruthy()
|
||||
const originalFileIntegrity = filesIndex.files['index.js'].integrity
|
||||
expect(originalFileIntegrity).toBeTruthy()
|
||||
// The integrity of the original file differs from the integrity of the patched file
|
||||
expect(originalFileIntegrity).not.toEqual(patchedFileIntegrity)
|
||||
|
||||
// The same with frozen lockfile
|
||||
await rimraf('node_modules')
|
||||
await install({
|
||||
dependencies: {
|
||||
'is-positive': '1.0.0',
|
||||
},
|
||||
}, {
|
||||
...opts,
|
||||
frozenLockfile: true,
|
||||
})
|
||||
expect(fs.readFileSync('node_modules/is-positive/index.js', 'utf8')).toContain('// patched')
|
||||
|
||||
// The same with frozen lockfile and hoisted node_modules
|
||||
await rimraf('node_modules')
|
||||
await install({
|
||||
dependencies: {
|
||||
'is-positive': '1.0.0',
|
||||
},
|
||||
}, {
|
||||
...opts,
|
||||
frozenLockfile: true,
|
||||
nodeLinker: 'hoisted',
|
||||
})
|
||||
expect(fs.readFileSync('node_modules/is-positive/index.js', 'utf8')).toContain('// patched')
|
||||
|
||||
process.chdir('..')
|
||||
fs.mkdirSync('project2')
|
||||
process.chdir('project2')
|
||||
|
||||
await install({
|
||||
dependencies: {
|
||||
'is-positive': '1.0.0',
|
||||
},
|
||||
}, await testDefaults({
|
||||
fastUnpack: false,
|
||||
sideEffectsCacheRead: true,
|
||||
sideEffectsCacheWrite: true,
|
||||
offline: true,
|
||||
}, {}, {}, { packageImportMethod: 'hardlink' }))
|
||||
|
||||
// The original file did not break, when a patched version was created
|
||||
expect(fs.readFileSync('node_modules/is-positive/index.js', 'utf8')).not.toContain('// patched')
|
||||
})
|
||||
@@ -22,7 +22,7 @@ function createPackageImporter (
|
||||
const packageImportMethod = opts.packageImportMethod
|
||||
const gfm = getFlatMap.bind(null, opts.cafsDir)
|
||||
return async (to, opts) => {
|
||||
const { filesMap, isBuilt } = gfm(opts.filesResponse, opts.targetEngine)
|
||||
const { filesMap, isBuilt } = gfm(opts.filesResponse, opts.sideEffectsCacheKey)
|
||||
const pkgImportMethod = (opts.requiresBuild && !isBuilt)
|
||||
? 'clone-or-copy'
|
||||
: (opts.filesResponse.packageImportMethod ?? packageImportMethod)
|
||||
|
||||
@@ -23,7 +23,7 @@ export type PackageFilesResponse = {
|
||||
|
||||
export interface ImportPackageOpts {
|
||||
requiresBuild?: boolean
|
||||
targetEngine?: string
|
||||
sideEffectsCacheKey?: string
|
||||
filesResponse: PackageFilesResponse
|
||||
force: boolean
|
||||
}
|
||||
|
||||
@@ -71,6 +71,7 @@
|
||||
"@pnpm/calc-dep-state": "workspace:2.0.1",
|
||||
"@pnpm/constants": "workspace:6.1.0",
|
||||
"@pnpm/core-loggers": "workspace:7.0.3",
|
||||
"@pnpm/crypto.base32-hash": "workspace:1.0.0",
|
||||
"@pnpm/error": "workspace:3.0.1",
|
||||
"@pnpm/filter-lockfile": "workspace:6.0.6",
|
||||
"@pnpm/hoist": "workspace:6.1.4",
|
||||
|
||||
@@ -679,15 +679,15 @@ async function linkAllPkgs (
|
||||
throw err
|
||||
}
|
||||
|
||||
let targetEngine: string | undefined
|
||||
let sideEffectsCacheKey: string | undefined
|
||||
if (opts.sideEffectsCacheRead && filesResponse.sideEffects && !isEmpty(filesResponse.sideEffects)) {
|
||||
targetEngine = calcDepState(depNode.dir, opts.depGraph, opts.depsStateCache)
|
||||
sideEffectsCacheKey = calcDepState(opts.depGraph, opts.depsStateCache, depNode.dir, depNode.patchFile?.hash)
|
||||
}
|
||||
const { importMethod, isBuilt } = await storeController.importPackage(depNode.dir, {
|
||||
filesResponse,
|
||||
force: opts.force,
|
||||
requiresBuild: depNode.requiresBuild,
|
||||
targetEngine,
|
||||
sideEffectsCacheKey,
|
||||
})
|
||||
if (importMethod) {
|
||||
progressLogger.debug({
|
||||
|
||||
@@ -87,6 +87,7 @@ async function linkAllPkgsInOrder (
|
||||
warn: (message: string) => void
|
||||
}
|
||||
) {
|
||||
const _calcDepState = calcDepState.bind(null, graph, opts.depsStateCache)
|
||||
await Promise.all(
|
||||
Object.entries(hierarchy).map(async ([dir, deps]) => {
|
||||
const depNode = graph[dir]
|
||||
@@ -98,15 +99,15 @@ async function linkAllPkgsInOrder (
|
||||
throw err
|
||||
}
|
||||
|
||||
let targetEngine: string | undefined
|
||||
let sideEffectsCacheKey: string | undefined
|
||||
if (opts.sideEffectsCacheRead && filesResponse.sideEffects && !isEmpty(filesResponse.sideEffects)) {
|
||||
targetEngine = calcDepState(dir, graph, opts.depsStateCache)
|
||||
sideEffectsCacheKey = _calcDepState(dir, depNode.patchFile?.hash)
|
||||
}
|
||||
const { importMethod, isBuilt } = await storeController.importPackage(depNode.dir, {
|
||||
filesResponse,
|
||||
force: opts.force || depNode.depPath !== prevGraph[dir]?.depPath,
|
||||
requiresBuild: depNode.requiresBuild,
|
||||
targetEngine,
|
||||
sideEffectsCacheKey,
|
||||
})
|
||||
if (importMethod) {
|
||||
progressLogger.debug({
|
||||
|
||||
@@ -3,6 +3,7 @@ import { WANTED_LOCKFILE } from '@pnpm/constants'
|
||||
import {
|
||||
progressLogger,
|
||||
} from '@pnpm/core-loggers'
|
||||
import { createBase32HashFromFile } from '@pnpm/crypto.base32-hash'
|
||||
import {
|
||||
Lockfile,
|
||||
PackageSnapshot,
|
||||
@@ -44,6 +45,10 @@ export interface DependenciesGraphNode {
|
||||
prepare: boolean
|
||||
hasBin: boolean
|
||||
filesIndexFile: string
|
||||
patchFile?: {
|
||||
path: string
|
||||
hash: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface DependenciesGraph {
|
||||
@@ -175,6 +180,7 @@ export default async function lockfileToDepGraph (
|
||||
optionalDependencies: new Set(Object.keys(pkgSnapshot.optionalDependencies ?? {})),
|
||||
prepare: pkgSnapshot.prepare === true,
|
||||
requiresBuild: pkgSnapshot.requiresBuild === true,
|
||||
patchFile: await tryReadPatchFile(opts.lockfileDir, lockfile, pkgName, pkgVersion),
|
||||
}
|
||||
pkgSnapshotByLocation[dir] = pkgSnapshot
|
||||
})
|
||||
@@ -214,6 +220,18 @@ export default async function lockfileToDepGraph (
|
||||
return { graph, directDependenciesByImporterId }
|
||||
}
|
||||
|
||||
export async function tryReadPatchFile (lockfileDir: string, lockfile: Lockfile, pkgName: string, pkgVersion: string) {
|
||||
const patchFileRelativePath = lockfile.patchedDependencies?.[`${pkgName}@${pkgVersion}`]
|
||||
if (!patchFileRelativePath) {
|
||||
return undefined
|
||||
}
|
||||
const patchFilePath = path.join(lockfileDir, patchFileRelativePath)
|
||||
return {
|
||||
path: patchFilePath,
|
||||
hash: await createBase32HashFromFile(patchFilePath),
|
||||
}
|
||||
}
|
||||
|
||||
async function getChildrenPaths (
|
||||
ctx: {
|
||||
graph: DependenciesGraph
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
DepHierarchy,
|
||||
DirectDependenciesByImporterId,
|
||||
LockfileToDepGraphResult,
|
||||
tryReadPatchFile,
|
||||
} from './lockfileToDepGraph'
|
||||
|
||||
export interface LockfileToHoistedDepGraphOptions {
|
||||
@@ -208,6 +209,7 @@ async function fetchDeps (
|
||||
optionalDependencies: new Set(Object.keys(pkgSnapshot.optionalDependencies ?? {})),
|
||||
prepare: pkgSnapshot.prepare === true,
|
||||
requiresBuild: pkgSnapshot.requiresBuild === true,
|
||||
patchFile: await tryReadPatchFile(opts.lockfileDir, opts.lockfile, pkgName, pkgVersion),
|
||||
}
|
||||
opts.pkgLocationByDepPath[depPath] = dir
|
||||
depHierarchy[dir] = await fetchDeps(opts, path.join(dir, 'node_modules'), dep.dependencies)
|
||||
|
||||
@@ -33,6 +33,9 @@
|
||||
{
|
||||
"path": "../core-loggers"
|
||||
},
|
||||
{
|
||||
"path": "../crypto.base32-hash"
|
||||
},
|
||||
{
|
||||
"path": "../dependency-path"
|
||||
},
|
||||
|
||||
@@ -25,6 +25,10 @@ export async function runPostinstallHooks (
|
||||
if (pkg.scripts == null) {
|
||||
pkg.scripts = {}
|
||||
}
|
||||
if (opts.patchPath) {
|
||||
pkg.scripts['pnpm:patch'] = `git apply ${opts.patchPath}`
|
||||
await runLifecycleHook('pnpm:patch', pkg, opts)
|
||||
}
|
||||
|
||||
if (!pkg.scripts.install) {
|
||||
await checkBindingGyp(opts.pkgRoot, pkg.scripts)
|
||||
@@ -42,7 +46,8 @@ export async function runPostinstallHooks (
|
||||
|
||||
return pkg.scripts.preinstall != null ||
|
||||
pkg.scripts.install != null ||
|
||||
pkg.scripts.postinstall != null
|
||||
pkg.scripts.postinstall != null ||
|
||||
Boolean(opts.patchPath)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,6 +12,7 @@ export interface RunLifecycleHookOptions {
|
||||
extraEnv?: Record<string, string>
|
||||
initCwd?: string
|
||||
optional?: boolean
|
||||
patchPath?: string
|
||||
pkgRoot: string
|
||||
rawConfig: object
|
||||
rootModulesDir: string
|
||||
|
||||
@@ -8,6 +8,7 @@ export interface Lockfile {
|
||||
onlyBuiltDependencies?: string[]
|
||||
overrides?: Record<string, string>
|
||||
packageExtensionsChecksum?: string
|
||||
patchedDependencies?: Record<string, string>
|
||||
}
|
||||
|
||||
export interface ProjectSnapshot {
|
||||
|
||||
@@ -53,23 +53,23 @@ export default async function (
|
||||
upload,
|
||||
}
|
||||
|
||||
async function upload (builtPkgLocation: string, opts: {filesIndexFile: string, engine: string}) {
|
||||
async function upload (builtPkgLocation: string, opts: {filesIndexFile: string, sideEffectsCacheKey: string}) {
|
||||
const sideEffectsIndex = await cafs.addFilesFromDir(builtPkgLocation)
|
||||
// TODO: move this to a function
|
||||
// This is duplicated in @pnpm/package-requester
|
||||
const integrity: Record<string, PackageFileInfo> = {}
|
||||
await Promise.all(
|
||||
Object.keys(sideEffectsIndex)
|
||||
.map(async (filename) => {
|
||||
Object.entries(sideEffectsIndex)
|
||||
.map(async ([filename, { writeResult, mode, size }]) => {
|
||||
const {
|
||||
checkedAt,
|
||||
integrity: fileIntegrity,
|
||||
} = await sideEffectsIndex[filename].writeResult
|
||||
} = await writeResult
|
||||
integrity[filename] = {
|
||||
checkedAt,
|
||||
integrity: fileIntegrity.toString(), // TODO: use the raw Integrity object
|
||||
mode: sideEffectsIndex[filename].mode,
|
||||
size: sideEffectsIndex[filename].size,
|
||||
mode,
|
||||
size,
|
||||
}
|
||||
})
|
||||
)
|
||||
@@ -80,7 +80,7 @@ export default async function (
|
||||
filesIndex = { files: integrity }
|
||||
}
|
||||
filesIndex.sideEffects = filesIndex.sideEffects ?? {}
|
||||
filesIndex.sideEffects[opts.engine] = integrity
|
||||
filesIndex.sideEffects[opts.sideEffectsCacheKey] = integrity
|
||||
await writeJsonFile(opts.filesIndexFile, filesIndex, { indent: undefined })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ export default function getOptionsFromRootManifest (manifest: ProjectManifest):
|
||||
neverBuiltDependencies?: string[]
|
||||
onlyBuiltDependencies?: string[]
|
||||
packageExtensions?: Record<string, PackageExtension>
|
||||
patchedDependencies?: Record<string, string>
|
||||
peerDependencyRules?: PeerDependencyRules
|
||||
} {
|
||||
// We read Yarn's resolutions field for compatibility
|
||||
@@ -22,6 +23,7 @@ export default function getOptionsFromRootManifest (manifest: ProjectManifest):
|
||||
const packageExtensions = manifest.pnpm?.packageExtensions
|
||||
const peerDependencyRules = manifest.pnpm?.peerDependencyRules
|
||||
const allowedDeprecatedVersions = manifest.pnpm?.allowedDeprecatedVersions
|
||||
const patchedDependencies = manifest.pnpm?.patchedDependencies
|
||||
return {
|
||||
allowedDeprecatedVersions,
|
||||
overrides,
|
||||
@@ -29,5 +31,6 @@ export default function getOptionsFromRootManifest (manifest: ProjectManifest):
|
||||
onlyBuiltDependencies,
|
||||
packageExtensions,
|
||||
peerDependencyRules,
|
||||
patchedDependencies,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
"dependencies": {
|
||||
"@pnpm/constants": "workspace:6.1.0",
|
||||
"@pnpm/core-loggers": "workspace:7.0.3",
|
||||
"@pnpm/crypto.base32-hash": "workspace:1.0.0",
|
||||
"@pnpm/error": "workspace:3.0.1",
|
||||
"@pnpm/lockfile-types": "workspace:4.0.3",
|
||||
"@pnpm/lockfile-utils": "workspace:4.0.5",
|
||||
|
||||
@@ -256,7 +256,8 @@ async function finishLockfileUpdates (
|
||||
Boolean(pkgJson.scripts.postinstall)
|
||||
) ||
|
||||
filesResponse.filesIndex['binding.gyp'] ||
|
||||
Object.keys(filesResponse.filesIndex).some((filename) => !(filename.match(/^[.]hooks[\\/]/) == null)) // TODO: optimize this
|
||||
Object.keys(filesResponse.filesIndex).some((filename) => !(filename.match(/^[.]hooks[\\/]/) == null)) || // TODO: optimize this
|
||||
depNode.patchFile != null
|
||||
)
|
||||
} else {
|
||||
// This should never ever happen
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
progressLogger,
|
||||
skippedOptionalDependencyLogger,
|
||||
} from '@pnpm/core-loggers'
|
||||
import { createBase32HashFromFile } from '@pnpm/crypto.base32-hash'
|
||||
import PnpmError from '@pnpm/error'
|
||||
import {
|
||||
Lockfile,
|
||||
@@ -129,6 +130,7 @@ export interface ResolutionContext {
|
||||
resolvedPackagesByDepPath: ResolvedPackagesByDepPath
|
||||
outdatedDependencies: {[pkgId: string]: string}
|
||||
childrenByParentDepPath: ChildrenByParentDepPath
|
||||
patchedDependencies?: Record<string, string>
|
||||
pendingNodes: PendingNode[]
|
||||
wantedLockfile: Lockfile
|
||||
currentLockfile: Lockfile
|
||||
@@ -195,6 +197,10 @@ export interface ResolvedPackage {
|
||||
optionalDependencies: Set<string>
|
||||
hasBin: boolean
|
||||
hasBundledDependencies: boolean
|
||||
patchFile?: {
|
||||
path: string
|
||||
hash: string
|
||||
}
|
||||
prepare: boolean
|
||||
depPath: string
|
||||
requiresBuild: boolean | SafePromiseDefer<boolean>
|
||||
@@ -915,12 +921,18 @@ async function resolveDependency (
|
||||
status: 'resolved',
|
||||
})
|
||||
|
||||
ctx.resolvedPackagesByDepPath[depPath] = getResolvedPackage({
|
||||
let patchPath = ctx.patchedDependencies?.[`${pkg.name}@${pkg.version}`]
|
||||
if (patchPath) {
|
||||
patchPath = path.join(ctx.lockfileDir, patchPath)
|
||||
}
|
||||
|
||||
ctx.resolvedPackagesByDepPath[depPath] = await getResolvedPackage({
|
||||
allowBuild: ctx.allowBuild,
|
||||
dependencyLockfile: currentPkg.dependencyLockfile,
|
||||
depPath,
|
||||
force: ctx.force,
|
||||
hasBin,
|
||||
patchPath,
|
||||
pkg,
|
||||
pkgResponse,
|
||||
prepare,
|
||||
@@ -1005,19 +1017,20 @@ function pkgIsLeaf (pkg: PackageManifest) {
|
||||
isEmpty(pkg.peerDependencies ?? {})
|
||||
}
|
||||
|
||||
function getResolvedPackage (
|
||||
async function getResolvedPackage (
|
||||
options: {
|
||||
allowBuild?: (pkgName: string) => boolean
|
||||
dependencyLockfile?: PackageSnapshot
|
||||
depPath: string
|
||||
force: boolean
|
||||
hasBin: boolean
|
||||
patchPath?: string
|
||||
pkg: PackageManifest
|
||||
pkgResponse: PackageResponse
|
||||
prepare: boolean
|
||||
wantedDependency: WantedDependency
|
||||
}
|
||||
) {
|
||||
): Promise<ResolvedPackage> {
|
||||
const peerDependencies = peerDependenciesWithoutOwn(options.pkg)
|
||||
|
||||
const requiresBuild = (options.allowBuild == null || options.allowBuild(options.pkg.name))
|
||||
@@ -1046,6 +1059,9 @@ function getResolvedPackage (
|
||||
name: options.pkg.name,
|
||||
optional: options.wantedDependency.optional,
|
||||
optionalDependencies: new Set(Object.keys(options.pkg.optionalDependencies ?? {})),
|
||||
patchFile: options.patchPath
|
||||
? { path: options.patchPath, hash: await createBase32HashFromFile(options.patchPath) }
|
||||
: undefined,
|
||||
peerDependencies: peerDependencies ?? {},
|
||||
peerDependenciesMeta: options.pkg.peerDependenciesMeta,
|
||||
prepare: options.prepare,
|
||||
|
||||
@@ -67,6 +67,7 @@ export interface ResolveDependenciesOptions {
|
||||
}
|
||||
nodeVersion: string
|
||||
registries: Registries
|
||||
patchedDependencies?: Record<string, string>
|
||||
pnpmVersion: string
|
||||
preferredVersions?: PreferredVersions
|
||||
preferWorkspacePackages?: boolean
|
||||
@@ -103,6 +104,7 @@ export default async function<T> (
|
||||
lockfileDir: opts.lockfileDir,
|
||||
nodeVersion: opts.nodeVersion,
|
||||
outdatedDependencies: {} as {[pkgId: string]: string},
|
||||
patchedDependencies: opts.patchedDependencies,
|
||||
pendingNodes: [] as PendingNode[],
|
||||
pnpmVersion: opts.pnpmVersion,
|
||||
preferWorkspacePackages: opts.preferWorkspacePackages,
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
{
|
||||
"path": "../core-loggers"
|
||||
},
|
||||
{
|
||||
"path": "../crypto.base32-hash"
|
||||
},
|
||||
{
|
||||
"path": "../dependency-path"
|
||||
},
|
||||
|
||||
@@ -46,7 +46,7 @@ export default async function (
|
||||
stop: async () => {
|
||||
await limitedFetch(`${remotePrefix}/stop`, {})
|
||||
},
|
||||
upload: async (builtPkgLocation: string, opts: {filesIndexFile: string, engine: string}) => {
|
||||
upload: async (builtPkgLocation: string, opts: {filesIndexFile: string, sideEffectsCacheKey: string}) => {
|
||||
await limitedFetch(`${remotePrefix}/upload`, {
|
||||
builtPkgLocation,
|
||||
opts,
|
||||
|
||||
@@ -167,7 +167,7 @@ test('server upload', async () => {
|
||||
const filesIndexFile = path.join(storeDir, 'test.example.com/fake-pkg/1.0.0.json')
|
||||
|
||||
await storeCtrl.upload(path.join(__dirname, 'side-effect-fake-dir'), {
|
||||
engine: fakeEngine,
|
||||
sideEffectsCacheKey: fakeEngine,
|
||||
filesIndexFile,
|
||||
})
|
||||
|
||||
@@ -199,7 +199,7 @@ test('disable server upload', async () => {
|
||||
let thrown = false
|
||||
try {
|
||||
await storeCtrl.upload(path.join(__dirname, 'side-effect-fake-dir'), {
|
||||
engine: fakeEngine,
|
||||
sideEffectsCacheKey: fakeEngine,
|
||||
filesIndexFile,
|
||||
})
|
||||
} catch (e) {
|
||||
|
||||
@@ -35,13 +35,20 @@ DependencyManifest,
|
||||
| 'version'
|
||||
>
|
||||
|
||||
export interface UploadPkgToStoreOpts {
|
||||
filesIndexFile: string
|
||||
sideEffectsCacheKey: string
|
||||
}
|
||||
|
||||
export type UploadPkgToStore = (builtPkgLocation: string, opts: UploadPkgToStoreOpts) => Promise<void>
|
||||
|
||||
export interface StoreController {
|
||||
requestPackage: RequestPackageFunction
|
||||
fetchPackage: FetchPackageToStoreFunction
|
||||
importPackage: ImportPackageFunction
|
||||
close: () => Promise<void>
|
||||
prune: () => Promise<void>
|
||||
upload: (builtPkgLocation: string, opts: {filesIndexFile: string, engine: string}) => Promise<void>
|
||||
upload: UploadPkgToStore
|
||||
}
|
||||
|
||||
export type FetchPackageToStoreFunction = (
|
||||
|
||||
@@ -50,6 +50,7 @@ export interface DependenciesMeta {
|
||||
[dependencyName: string]: {
|
||||
injected?: boolean
|
||||
node?: string
|
||||
patch?: string
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,6 +127,7 @@ export type ProjectManifest = BaseManifest & {
|
||||
packageExtensions?: Record<string, PackageExtension>
|
||||
peerDependencyRules?: PeerDependencyRules
|
||||
allowedDeprecatedVersions?: AllowedDeprecatedVersions
|
||||
patchedDependencies?: Record<string, string>
|
||||
}
|
||||
private?: boolean
|
||||
resolutions?: Record<string, string>
|
||||
|
||||
4
pnpm-lock.yaml
generated
4
pnpm-lock.yaml
generated
@@ -1125,6 +1125,7 @@ importers:
|
||||
'@pnpm/client': workspace:7.1.5
|
||||
'@pnpm/constants': workspace:6.1.0
|
||||
'@pnpm/core-loggers': workspace:7.0.3
|
||||
'@pnpm/crypto.base32-hash': workspace:1.0.0
|
||||
'@pnpm/error': workspace:3.0.1
|
||||
'@pnpm/filter-lockfile': workspace:6.0.6
|
||||
'@pnpm/headless': workspace:18.2.0
|
||||
@@ -1173,6 +1174,7 @@ importers:
|
||||
'@pnpm/calc-dep-state': link:../calc-dep-state
|
||||
'@pnpm/constants': link:../constants
|
||||
'@pnpm/core-loggers': link:../core-loggers
|
||||
'@pnpm/crypto.base32-hash': link:../crypto.base32-hash
|
||||
'@pnpm/error': link:../error
|
||||
'@pnpm/filter-lockfile': link:../filter-lockfile
|
||||
'@pnpm/hoist': link:../hoist
|
||||
@@ -3211,6 +3213,7 @@ importers:
|
||||
specifiers:
|
||||
'@pnpm/constants': workspace:6.1.0
|
||||
'@pnpm/core-loggers': workspace:7.0.3
|
||||
'@pnpm/crypto.base32-hash': workspace:1.0.0
|
||||
'@pnpm/error': workspace:3.0.1
|
||||
'@pnpm/lockfile-types': workspace:4.0.3
|
||||
'@pnpm/lockfile-utils': workspace:4.0.5
|
||||
@@ -3246,6 +3249,7 @@ importers:
|
||||
dependencies:
|
||||
'@pnpm/constants': link:../constants
|
||||
'@pnpm/core-loggers': link:../core-loggers
|
||||
'@pnpm/crypto.base32-hash': link:../crypto.base32-hash
|
||||
'@pnpm/error': link:../error
|
||||
'@pnpm/lockfile-types': link:../lockfile-types
|
||||
'@pnpm/lockfile-utils': link:../lockfile-utils
|
||||
|
||||
Reference in New Issue
Block a user