feat(core): overrides, packageExtensions, and neverBuiltDependencies are options (#4050)

This commit is contained in:
Zoltan Kochan
2021-11-30 18:01:11 +02:00
committed by GitHub
parent 112d713952
commit 8a99a01ff6
23 changed files with 152 additions and 121 deletions

View File

@@ -0,0 +1,5 @@
---
"@pnpm/plugin-commands-installation": major
---
Pass `packageExtensions`, `overrides`, and `neverBuiltDependencies` to the core API. Take this information from `rootProjectManifest`, which should be passed in via the options.

View File

@@ -0,0 +1,5 @@
---
"@pnpm/core": major
---
`packageExtensions`, `overrides`, and `neverBuiltDependencies` are passed through as options to the core API. These settings are not read from the root manifest's `package.json`.

View File

@@ -0,0 +1,5 @@
---
"@pnpm/config": minor
---
Read the root project manifest and write it to the config object.

View File

@@ -36,6 +36,7 @@
"@pnpm/error": "workspace:2.0.0", "@pnpm/error": "workspace:2.0.0",
"@pnpm/global-bin-dir": "workspace:3.0.0", "@pnpm/global-bin-dir": "workspace:3.0.0",
"@pnpm/pnpmfile": "workspace:1.2.0", "@pnpm/pnpmfile": "workspace:1.2.0",
"@pnpm/read-project-manifest": "workspace:2.0.7",
"@pnpm/types": "workspace:7.6.0", "@pnpm/types": "workspace:7.6.0",
"@zkochan/npm-conf": "2.0.2", "@zkochan/npm-conf": "2.0.2",
"camelcase": "^6.2.0", "camelcase": "^6.2.0",

View File

@@ -1,5 +1,6 @@
import { import {
Project, Project,
ProjectManifest,
ProjectsGraph, ProjectsGraph,
Registries, Registries,
} from '@pnpm/types' } from '@pnpm/types'
@@ -140,6 +141,7 @@ export interface Config {
testPattern?: string[] testPattern?: string[]
changedFilesIgnorePattern?: string[] changedFilesIgnorePattern?: string[]
extendNodePath?: boolean extendNodePath?: boolean
rootProjectManifest?: ProjectManifest
} }
export interface ConfigWithDeprecatedSettings extends Config { export interface ConfigWithDeprecatedSettings extends Config {

View File

@@ -5,6 +5,7 @@ import { LAYOUT_VERSION } from '@pnpm/constants'
import PnpmError from '@pnpm/error' import PnpmError from '@pnpm/error'
import globalBinDir from '@pnpm/global-bin-dir' import globalBinDir from '@pnpm/global-bin-dir'
import { requireHooks } from '@pnpm/pnpmfile' import { requireHooks } from '@pnpm/pnpmfile'
import { safeReadProjectManifestOnly } from '@pnpm/read-project-manifest'
import camelcase from 'camelcase' import camelcase from 'camelcase'
import loadNpmConf from '@zkochan/npm-conf' import loadNpmConf from '@zkochan/npm-conf'
import npmTypes from '@zkochan/npm-conf/lib/types' import npmTypes from '@zkochan/npm-conf/lib/types'
@@ -483,6 +484,7 @@ export default async (
if (!pnpmConfig.ignorePnpmfile) { if (!pnpmConfig.ignorePnpmfile) {
pnpmConfig.hooks = requireHooks(pnpmConfig.lockfileDir ?? pnpmConfig.dir, pnpmConfig) pnpmConfig.hooks = requireHooks(pnpmConfig.lockfileDir ?? pnpmConfig.dir, pnpmConfig)
} }
pnpmConfig.rootProjectManifest = await safeReadProjectManifestOnly(pnpmConfig.lockfileDir ?? pnpmConfig.dir) ?? undefined
return { config: pnpmConfig, warnings } return { config: pnpmConfig, warnings }
} }

View File

@@ -24,6 +24,9 @@
{ {
"path": "../pnpmfile" "path": "../pnpmfile"
}, },
{
"path": "../read-project-manifest"
},
{ {
"path": "../types" "path": "../types"
} }

View File

@@ -6,6 +6,7 @@ import normalizeRegistries, { DEFAULT_REGISTRIES } from '@pnpm/normalize-registr
import { WorkspacePackages } from '@pnpm/resolver-base' import { WorkspacePackages } from '@pnpm/resolver-base'
import { StoreController } from '@pnpm/store-controller-types' import { StoreController } from '@pnpm/store-controller-types'
import { import {
PackageExtension,
ReadPackageHook, ReadPackageHook,
Registries, Registries,
} from '@pnpm/types' } from '@pnpm/types'
@@ -44,8 +45,10 @@ export interface StrictInstallOptions {
rawConfig: object rawConfig: object
verifyStoreIntegrity: boolean verifyStoreIntegrity: boolean
engineStrict: boolean engineStrict: boolean
neverBuiltDependencies: string[]
nodeExecPath?: string nodeExecPath?: string
nodeVersion: string nodeVersion: string
packageExtensions: Record<string, PackageExtension>
packageManager: { packageManager: {
name: string name: string
version: string version: string
@@ -67,6 +70,7 @@ export interface StrictInstallOptions {
unsafePerm: boolean unsafePerm: boolean
registries: Registries registries: Registries
tag: string tag: string
overrides: Record<string, string>
ownLifecycleHooksStdio: 'inherit' | 'pipe' ownLifecycleHooksStdio: 'inherit' | 'pipe'
workspacePackages: WorkspacePackages workspacePackages: WorkspacePackages
pruneStore: boolean pruneStore: boolean
@@ -120,9 +124,12 @@ const defaults = async (opts: InstallOptions) => {
}, },
lockfileDir: opts.lockfileDir ?? opts.dir ?? process.cwd(), lockfileDir: opts.lockfileDir ?? opts.dir ?? process.cwd(),
lockfileOnly: false, lockfileOnly: false,
neverBuiltDependencies: [] as string[],
nodeVersion: process.version, nodeVersion: process.version,
overrides: {},
ownLifecycleHooksStdio: 'inherit', ownLifecycleHooksStdio: 'inherit',
ignorePackageManifest: false, ignorePackageManifest: false,
packageExtensions: {},
packageManager, packageManager,
preferFrozenLockfile: true, preferFrozenLockfile: true,
preferWorkspacePackages: false, preferWorkspacePackages: false,

View File

@@ -166,19 +166,11 @@ export async function mutateModules (
// When running install/update on a subset of projects, the root project might not be included, // When running install/update on a subset of projects, the root project might not be included,
// so reading its manifest explicitly here. // so reading its manifest explicitly here.
await safeReadProjectManifestOnly(opts.lockfileDir) await safeReadProjectManifestOnly(opts.lockfileDir)
// We read Yarn's resolutions field for compatibility
// but we really replace the version specs to any other version spec, not only to exact versions,
// so we cannot call it resolutions
const overrides = (rootProjectManifest != null)
? rootProjectManifest.pnpm?.overrides ?? rootProjectManifest.resolutions
: undefined
const neverBuiltDependencies = rootProjectManifest?.pnpm?.neverBuiltDependencies ?? []
const packageExtensions = rootProjectManifest?.pnpm?.packageExtensions
opts.hooks.readPackage = createReadPackageHook({ opts.hooks.readPackage = createReadPackageHook({
readPackageHook: opts.hooks.readPackage, readPackageHook: opts.hooks.readPackage,
overrides, overrides: opts.overrides,
lockfileDir: opts.lockfileDir, lockfileDir: opts.lockfileDir,
packageExtensions, packageExtensions: opts.packageExtensions,
}) })
const ctx = await getContext(projects, opts) const ctx = await getContext(projects, opts)
const pruneVirtualStore = ctx.modulesFile?.prunedAt && opts.modulesCacheMaxAge > 0 const pruneVirtualStore = ctx.modulesFile?.prunedAt && opts.modulesCacheMaxAge > 0
@@ -225,15 +217,15 @@ export async function mutateModules (
} }
) )
} }
const packageExtensionsChecksum = isEmpty(packageExtensions ?? {}) ? undefined : createObjectChecksum(packageExtensions!) const packageExtensionsChecksum = isEmpty(opts.packageExtensions ?? {}) ? undefined : createObjectChecksum(opts.packageExtensions!)
let needsFullResolution = !maybeOpts.ignorePackageManifest && ( let needsFullResolution = !maybeOpts.ignorePackageManifest && (
!equals(ctx.wantedLockfile.overrides ?? {}, overrides ?? {}) || !equals(ctx.wantedLockfile.overrides ?? {}, opts.overrides ?? {}) ||
!equals((ctx.wantedLockfile.neverBuiltDependencies ?? []).sort(), (neverBuiltDependencies ?? []).sort()) || !equals((ctx.wantedLockfile.neverBuiltDependencies ?? []).sort(), (opts.neverBuiltDependencies ?? []).sort()) ||
ctx.wantedLockfile.packageExtensionsChecksum !== packageExtensionsChecksum) || ctx.wantedLockfile.packageExtensionsChecksum !== packageExtensionsChecksum) ||
opts.fixLockfile opts.fixLockfile
if (needsFullResolution) { if (needsFullResolution) {
ctx.wantedLockfile.overrides = overrides ctx.wantedLockfile.overrides = opts.overrides
ctx.wantedLockfile.neverBuiltDependencies = neverBuiltDependencies ctx.wantedLockfile.neverBuiltDependencies = opts.neverBuiltDependencies
ctx.wantedLockfile.packageExtensionsChecksum = packageExtensionsChecksum ctx.wantedLockfile.packageExtensionsChecksum = packageExtensionsChecksum
} }
const frozenLockfile = opts.frozenLockfile || const frozenLockfile = opts.frozenLockfile ||
@@ -496,8 +488,6 @@ export async function mutateModules (
currentLockfileIsUpToDate: !ctx.existsWantedLockfile || ctx.currentLockfileIsUpToDate, currentLockfileIsUpToDate: !ctx.existsWantedLockfile || ctx.currentLockfileIsUpToDate,
makePartialCurrentLockfile, makePartialCurrentLockfile,
needsFullResolution, needsFullResolution,
neverBuiltDependencies,
overrides,
pruneVirtualStore, pruneVirtualStore,
updateLockfileMinorVersion: true, updateLockfileMinorVersion: true,
}) })

View File

@@ -446,9 +446,9 @@ test('scripts have access to unlisted bins when hoisting is used', async () => {
test('selectively ignore scripts in some dependencies', async () => { test('selectively ignore scripts in some dependencies', async () => {
const project = prepareEmpty() const project = prepareEmpty()
const neverBuiltDependencies = ['pre-and-postinstall-scripts-example'] const neverBuiltDependencies = ['pre-and-postinstall-scripts-example']
const manifest = await addDependenciesToPackage({ pnpm: { neverBuiltDependencies } }, const manifest = await addDependenciesToPackage({},
['pre-and-postinstall-scripts-example', 'install-script-example'], ['pre-and-postinstall-scripts-example', 'install-script-example'],
await testDefaults({ fastUnpack: false }) await testDefaults({ fastUnpack: false, neverBuiltDependencies })
) )
expect(await exists('node_modules/pre-and-postinstall-scripts-example/generated-by-preinstall.js')).toBeFalsy() expect(await exists('node_modules/pre-and-postinstall-scripts-example/generated-by-preinstall.js')).toBeFalsy()
@@ -462,7 +462,7 @@ test('selectively ignore scripts in some dependencies', async () => {
await rimraf('node_modules') await rimraf('node_modules')
await install(manifest, await testDefaults({ fastUnpack: false, frozenLockfile: true })) await install(manifest, await testDefaults({ fastUnpack: false, frozenLockfile: true, neverBuiltDependencies }))
expect(await exists('node_modules/pre-and-postinstall-scripts-example/generated-by-preinstall.js')).toBeFalsy() expect(await exists('node_modules/pre-and-postinstall-scripts-example/generated-by-preinstall.js')).toBeFalsy()
expect(await exists('node_modules/pre-and-postinstall-scripts-example/generated-by-postinstall.js')).toBeFalsy() expect(await exists('node_modules/pre-and-postinstall-scripts-example/generated-by-postinstall.js')).toBeFalsy()
@@ -484,7 +484,6 @@ test('lockfile is updated if neverBuiltDependencies is changed', async () => {
} }
const neverBuiltDependencies = ['pre-and-postinstall-scripts-example'] const neverBuiltDependencies = ['pre-and-postinstall-scripts-example']
manifest.pnpm = { neverBuiltDependencies }
await mutateModules([ await mutateModules([
{ {
buildIndex: 0, buildIndex: 0,
@@ -492,7 +491,7 @@ test('lockfile is updated if neverBuiltDependencies is changed', async () => {
mutation: 'install', mutation: 'install',
rootDir: process.cwd(), rootDir: process.cwd(),
}, },
], await testDefaults()) ], await testDefaults({ neverBuiltDependencies }))
{ {
const lockfile = await project.readLockfile() const lockfile = await project.readLockfile()

View File

@@ -1174,16 +1174,6 @@ test('resolve a subdependency from the workspace and use it as a peer', async ()
test('resolve a subdependency from the workspace, when it uses the workspace protocol', async () => { test('resolve a subdependency from the workspace, when it uses the workspace protocol', async () => {
preparePackages([ preparePackages([
{
location: '.',
package: {
pnpm: {
overrides: {
'dep-of-pkg-with-1-dep': 'workspace:*',
},
},
},
},
{ {
location: 'project', location: 'project',
package: { name: 'project' }, package: { name: 'project' },
@@ -1229,7 +1219,14 @@ test('resolve a subdependency from the workspace, when it uses the workspace pro
}, },
}, },
} }
await mutateModules(importers, await testDefaults({ linkWorkspacePackagesDepth: -1, workspacePackages })) const overrides = {
'dep-of-pkg-with-1-dep': 'workspace:*',
}
await mutateModules(importers, await testDefaults({
linkWorkspacePackagesDepth: -1,
overrides,
workspacePackages,
}))
const project = assertProject(process.cwd()) const project = assertProject(process.cwd())
@@ -1241,6 +1238,7 @@ test('resolve a subdependency from the workspace, when it uses the workspace pro
// Testing that headless installation does not fail with links in subdeps // Testing that headless installation does not fail with links in subdeps
await mutateModules(importers, await testDefaults({ await mutateModules(importers, await testDefaults({
frozenLockfile: true, frozenLockfile: true,
overrides,
workspacePackages, workspacePackages,
})) }))
}) })

View File

@@ -6,21 +6,21 @@ import {
testDefaults, testDefaults,
} from '../utils' } from '../utils'
test('versions are replaced with versions specified through pnpm.overrides field', async () => { test('versions are replaced with versions specified through overrides option', async () => {
const project = prepareEmpty() const project = prepareEmpty()
await addDistTag({ package: 'bar', version: '100.0.0', distTag: 'latest' }) await addDistTag({ package: 'bar', version: '100.0.0', distTag: 'latest' })
await addDistTag({ package: 'foo', version: '100.0.0', distTag: 'latest' }) await addDistTag({ package: 'foo', version: '100.0.0', distTag: 'latest' })
const manifest = await addDependenciesToPackage({ const overrides = {
pnpm: { 'foobarqar>foo': 'npm:qar@100.0.0',
overrides: { 'bar@^100.0.0': '100.1.0',
'foobarqar>foo': 'npm:qar@100.0.0', 'dep-of-pkg-with-1-dep': '101.0.0',
'bar@^100.0.0': '100.1.0', }
'dep-of-pkg-with-1-dep': '101.0.0', const manifest = await addDependenciesToPackage({},
}, ['pkg-with-1-dep@100.0.0', 'foobar@100.0.0', 'foobarqar@1.0.0'],
}, await testDefaults({ overrides })
}, ['pkg-with-1-dep@100.0.0', 'foobar@100.0.0', 'foobarqar@1.0.0'], await testDefaults()) )
{ {
const lockfile = await project.readLockfile() const lockfile = await project.readLockfile()
@@ -44,12 +44,12 @@ test('versions are replaced with versions specified through pnpm.overrides field
mutation: 'install', mutation: 'install',
rootDir: process.cwd(), rootDir: process.cwd(),
}, },
], { ...await testDefaults(), ignorePackageManifest: true }) ], { ...await testDefaults(), ignorePackageManifest: true, overrides })
// The lockfile is updated if the overrides are changed // The lockfile is updated if the overrides are changed
manifest.pnpm!.overrides!['bar@^100.0.0'] = '100.0.0' overrides['bar@^100.0.0'] = '100.0.0'
// A direct dependency may be overriden as well // A direct dependency may be overriden as well
manifest.pnpm!.overrides!['foobarqar'] = '1.0.1' overrides['foobarqar'] = '1.0.1'
await mutateModules([ await mutateModules([
{ {
buildIndex: 0, buildIndex: 0,
@@ -57,7 +57,7 @@ test('versions are replaced with versions specified through pnpm.overrides field
mutation: 'install', mutation: 'install',
rootDir: process.cwd(), rootDir: process.cwd(),
}, },
], await testDefaults()) ], await testDefaults({ overrides }))
{ {
const lockfile = await project.readLockfile() const lockfile = await project.readLockfile()
@@ -81,7 +81,7 @@ test('versions are replaced with versions specified through pnpm.overrides field
mutation: 'install', mutation: 'install',
rootDir: process.cwd(), rootDir: process.cwd(),
}, },
], await testDefaults({ frozenLockfile: true })) ], await testDefaults({ frozenLockfile: true, overrides }))
{ {
const lockfile = await project.readLockfile() const lockfile = await project.readLockfile()
@@ -95,7 +95,7 @@ test('versions are replaced with versions specified through pnpm.overrides field
expect(lockfile.overrides).toStrictEqual(currentLockfile.overrides) expect(lockfile.overrides).toStrictEqual(currentLockfile.overrides)
} }
manifest.pnpm!.overrides!['bar@^100.0.0'] = '100.0.1' overrides['bar@^100.0.0'] = '100.0.1'
await expect( await expect(
mutateModules([ mutateModules([
{ {
@@ -104,54 +104,10 @@ test('versions are replaced with versions specified through pnpm.overrides field
mutation: 'install', mutation: 'install',
rootDir: process.cwd(), rootDir: process.cwd(),
}, },
], await testDefaults({ frozenLockfile: true })) ], await testDefaults({ frozenLockfile: true, overrides }))
).rejects.toThrow( ).rejects.toThrow(
new PnpmError('FROZEN_LOCKFILE_WITH_OUTDATED_LOCKFILE', new PnpmError('FROZEN_LOCKFILE_WITH_OUTDATED_LOCKFILE',
'Cannot perform a frozen installation because the lockfile needs updates' 'Cannot perform a frozen installation because the lockfile needs updates'
) )
) )
}) })
test('versions are replaced with versions specified through "resolutions" field (for Yarn compatibility)', async () => {
const project = prepareEmpty()
await addDistTag({ package: 'bar', version: '100.0.0', distTag: 'latest' })
const manifest = await addDependenciesToPackage({
resolutions: {
'bar@^100.0.0': '100.1.0',
'dep-of-pkg-with-1-dep': '101.0.0',
},
}, ['pkg-with-1-dep@100.0.0', 'foobar@100.0.0'], await testDefaults())
{
const lockfile = await project.readLockfile()
expect(lockfile.packages).toHaveProperty(['/dep-of-pkg-with-1-dep/101.0.0'])
expect(lockfile.packages).toHaveProperty(['/bar/100.1.0'])
expect(lockfile.overrides).toStrictEqual({
'bar@^100.0.0': '100.1.0',
'dep-of-pkg-with-1-dep': '101.0.0',
})
}
// The lockfile is updated if the resolutions are changed
manifest.resolutions!['bar@^100.0.0'] = '100.0.0'
await mutateModules([
{
buildIndex: 0,
manifest,
mutation: 'install',
rootDir: process.cwd(),
},
], await testDefaults())
{
const lockfile = await project.readLockfile()
expect(lockfile.packages).toHaveProperty(['/dep-of-pkg-with-1-dep/101.0.0'])
expect(lockfile.packages).toHaveProperty(['/bar/100.0.0'])
expect(lockfile.overrides).toStrictEqual({
'bar@^100.0.0': '100.0.0',
'dep-of-pkg-with-1-dep': '101.0.0',
})
}
})

View File

@@ -6,20 +6,21 @@ import {
testDefaults, testDefaults,
} from '../utils' } from '../utils'
test('manifests are extended with fields specified by pnpm.packageExtensions', async () => { test('manifests are extended with fields specified by packageExtensions', async () => {
const project = prepareEmpty() const project = prepareEmpty()
const manifest = await addDependenciesToPackage({ const packageExtensions = {
pnpm: { 'is-positive': {
packageExtensions: { dependencies: {
'is-positive': { bar: '100.1.0',
dependencies: {
bar: '100.1.0',
},
},
}, },
}, },
}, ['is-positive@1.0.0'], await testDefaults()) }
const manifest = await addDependenciesToPackage(
{},
['is-positive@1.0.0'],
await testDefaults({ packageExtensions })
)
{ {
const lockfile = await project.readLockfile() const lockfile = await project.readLockfile()
@@ -36,7 +37,7 @@ test('manifests are extended with fields specified by pnpm.packageExtensions', a
} }
// The lockfile is updated if the overrides are changed // The lockfile is updated if the overrides are changed
manifest.pnpm!.packageExtensions!['is-positive'].dependencies!['foobar'] = '100.0.0' packageExtensions['is-positive'].dependencies!['foobar'] = '100.0.0'
await mutateModules([ await mutateModules([
{ {
buildIndex: 0, buildIndex: 0,
@@ -44,7 +45,7 @@ test('manifests are extended with fields specified by pnpm.packageExtensions', a
mutation: 'install', mutation: 'install',
rootDir: process.cwd(), rootDir: process.cwd(),
}, },
], await testDefaults()) ], await testDefaults({ packageExtensions }))
{ {
const lockfile = await project.readLockfile() const lockfile = await project.readLockfile()
@@ -68,7 +69,7 @@ test('manifests are extended with fields specified by pnpm.packageExtensions', a
mutation: 'install', mutation: 'install',
rootDir: process.cwd(), rootDir: process.cwd(),
}, },
], await testDefaults({ frozenLockfile: true })) ], await testDefaults({ frozenLockfile: true, packageExtensions }))
{ {
const lockfile = await project.readLockfile() const lockfile = await project.readLockfile()
@@ -84,7 +85,7 @@ test('manifests are extended with fields specified by pnpm.packageExtensions', a
expect(lockfile.packageExtensionsChecksum).toStrictEqual(currentLockfile.packageExtensionsChecksum) expect(lockfile.packageExtensionsChecksum).toStrictEqual(currentLockfile.packageExtensionsChecksum)
} }
manifest.pnpm!.packageExtensions!['is-positive'].dependencies!['bar'] = '100.0.1' packageExtensions['is-positive'].dependencies!['bar'] = '100.0.1'
await expect( await expect(
mutateModules([ mutateModules([
{ {
@@ -93,7 +94,7 @@ test('manifests are extended with fields specified by pnpm.packageExtensions', a
mutation: 'install', mutation: 'install',
rootDir: process.cwd(), rootDir: process.cwd(),
}, },
], await testDefaults({ frozenLockfile: true })) ], await testDefaults({ frozenLockfile: true, packageExtensions }))
).rejects.toThrow( ).rejects.toThrow(
new PnpmError('FROZEN_LOCKFILE_WITH_OUTDATED_LOCKFILE', new PnpmError('FROZEN_LOCKFILE_WITH_OUTDATED_LOCKFILE',
'Cannot perform a frozen installation because the lockfile needs updates' 'Cannot perform a frozen installation because the lockfile needs updates'

View File

@@ -0,0 +1,15 @@
import { ProjectManifest } from '@pnpm/types'
export default function getOptionsFromRootManifest (manifest: ProjectManifest) {
// We read Yarn's resolutions field for compatibility
// but we really replace the version specs to any other version spec, not only to exact versions,
// so we cannot call it resolutions
const overrides = manifest.pnpm?.overrides ?? manifest.resolutions
const neverBuiltDependencies = manifest.pnpm?.neverBuiltDependencies ?? []
const packageExtensions = manifest.pnpm?.packageExtensions
return {
overrides,
neverBuiltDependencies,
packageExtensions,
}
}

View File

@@ -21,6 +21,7 @@ import { parse as parseYarnLock } from '@yarnpkg/lockfile'
import * as yarnCore from '@yarnpkg/core' import * as yarnCore from '@yarnpkg/core'
import { parseSyml } from '@yarnpkg/parsers' import { parseSyml } from '@yarnpkg/parsers'
import exists from 'path-exists' import exists from 'path-exists'
import getOptionsFromRootManifest from '../getOptionsFromRootManifest'
import recursive from '../recursive' import recursive from '../recursive'
import { yarnLockFileKeyNormalizer } from './yarnUtil' import { yarnLockFileKeyNormalizer } from './yarnUtil'
@@ -143,14 +144,16 @@ export async function handler (
} }
const store = await createOrConnectStoreController(opts) const store = await createOrConnectStoreController(opts)
const manifest = await readProjectManifestOnly(opts.dir)
const installOpts = { const installOpts = {
...opts, ...opts,
...getOptionsFromRootManifest(manifest),
lockfileOnly: true, lockfileOnly: true,
preferredVersions, preferredVersions,
storeController: store.ctrl, storeController: store.ctrl,
storeDir: store.dir, storeDir: store.dir,
} }
await install(await readProjectManifestOnly(opts.dir), installOpts) await install(manifest, installOpts)
} }
async function readYarnLockFile (dir: string) { async function readYarnLockFile (dir: string) {
@@ -279,4 +282,4 @@ function getYarnLockfileType (
return lockFileContents.includes('__metadata') return lockFileContents.includes('__metadata')
? YarnLockType.yarn2 ? YarnLockType.yarn2
: YarnLockType.yarn : YarnLockType.yarn
} }

View File

@@ -18,6 +18,7 @@ import {
import logger from '@pnpm/logger' import logger from '@pnpm/logger'
import { sequenceGraph } from '@pnpm/sort-packages' import { sequenceGraph } from '@pnpm/sort-packages'
import isSubdir from 'is-subdir' import isSubdir from 'is-subdir'
import getOptionsFromRootManifest from './getOptionsFromRootManifest'
import getPinnedVersion from './getPinnedVersion' import getPinnedVersion from './getPinnedVersion'
import getSaveType from './getSaveType' import getSaveType from './getSaveType'
import getNodeExecPath from './nodeExecPath' import getNodeExecPath from './nodeExecPath'
@@ -161,9 +162,18 @@ when running add/update with the --workspace option')
workspacePackages = arrayOfWorkspacePackagesToMap(allProjects) workspacePackages = arrayOfWorkspacePackagesToMap(allProjects)
} }
let { manifest, writeProjectManifest } = await tryReadProjectManifest(opts.dir, opts)
if (manifest === null) {
if (opts.update) {
throw new PnpmError('NO_IMPORTER_MANIFEST', 'No package.json found')
}
manifest = {}
}
const store = await createOrConnectStoreController(opts) const store = await createOrConnectStoreController(opts)
const installOpts = { const installOpts = {
...opts, ...opts,
...getOptionsFromRootManifest(manifest),
forceHoistPattern, forceHoistPattern,
forcePublicHoistPattern, forcePublicHoistPattern,
// In case installation is done in a multi-package repository // In case installation is done in a multi-package repository
@@ -184,14 +194,6 @@ when running add/update with the --workspace option')
} }
} }
let { manifest, writeProjectManifest } = await tryReadProjectManifest(opts.dir, opts)
if (manifest === null) {
if (opts.update) {
throw new PnpmError('NO_IMPORTER_MANIFEST', 'No package.json found')
}
manifest = {}
}
const updateMatch = opts.update && (params.length > 0) ? createMatcher(params) : null const updateMatch = opts.update && (params.length > 0) ? createMatcher(params) : null
if (updateMatch != null) { if (updateMatch != null) {
params = matchDependencies(updateMatch, manifest, includeDirect) params = matchDependencies(updateMatch, manifest, includeDirect)

View File

@@ -27,6 +27,7 @@ import pick from 'ramda/src/pick'
import partition from 'ramda/src/partition' import partition from 'ramda/src/partition'
import renderHelp from 'render-help' import renderHelp from 'render-help'
import * as installCommand from './install' import * as installCommand from './install'
import getOptionsFromRootManifest from './getOptionsFromRootManifest'
import getSaveType from './getSaveType' import getSaveType from './getSaveType'
const isWindows = process.platform === 'win32' || global['FAKE_WINDOWS'] const isWindows = process.platform === 'win32' || global['FAKE_WINDOWS']
@@ -140,6 +141,7 @@ export async function handler (
await install( await install(
await readProjectManifestOnly(dir, opts), { await readProjectManifestOnly(dir, opts), {
...config, ...config,
...getOptionsFromRootManifest(config.rootProjectManifest ?? {}),
include: { include: {
dependencies: config.production !== false, dependencies: config.production !== false,
devDependencies: config.dev !== false, devDependencies: config.dev !== false,

View File

@@ -5,6 +5,7 @@ import { createOrConnectStoreController, CreateStoreControllerOptions } from '@p
import { InstallOptions, mutateModules } from '@pnpm/core' import { InstallOptions, mutateModules } from '@pnpm/core'
import pick from 'ramda/src/pick' import pick from 'ramda/src/pick'
import renderHelp from 'render-help' import renderHelp from 'render-help'
import getOptionsFromRootManifest from './getOptionsFromRootManifest'
export const rcOptionsTypes = cliOptionsTypes export const rcOptionsTypes = cliOptionsTypes
@@ -44,19 +45,21 @@ export function help () {
} }
export async function handler ( export async function handler (
opts: Pick<Config, 'dev' | 'engineStrict' | 'optional' | 'production'> & CreateStoreControllerOptions opts: Pick<Config, 'dev' | 'engineStrict' | 'optional' | 'production' | 'rootProjectManifest'> & CreateStoreControllerOptions
) { ) {
const store = await createOrConnectStoreController(opts) const store = await createOrConnectStoreController(opts)
const manifest = await readProjectManifestOnly(process.cwd(), opts)
return mutateModules([ return mutateModules([
{ {
buildIndex: 0, buildIndex: 0,
manifest: await readProjectManifestOnly(process.cwd(), opts), manifest,
mutation: 'install', mutation: 'install',
pruneDirectDependencies: true, pruneDirectDependencies: true,
rootDir: process.cwd(), rootDir: process.cwd(),
}, },
], { ], {
...opts, ...opts,
...getOptionsFromRootManifest(opts.rootProjectManifest ?? {}),
include: { include: {
dependencies: opts.production !== false, dependencies: opts.production !== false,
devDependencies: opts.dev !== false, devDependencies: opts.dev !== false,

View File

@@ -34,6 +34,7 @@ import mem from 'mem'
import pFilter from 'p-filter' import pFilter from 'p-filter'
import pLimit from 'p-limit' import pLimit from 'p-limit'
import readIniFile from 'read-ini-file' import readIniFile from 'read-ini-file'
import getOptionsFromRootManifest from './getOptionsFromRootManifest'
import { createWorkspaceSpecs, updateToWorkspacePackagesFromManifest } from './updateWorkspaceDependencies' import { createWorkspaceSpecs, updateToWorkspacePackagesFromManifest } from './updateWorkspaceDependencies'
import updateToLatestSpecsFromManifest, { createLatestSpecs } from './updateToLatestSpecsFromManifest' import updateToLatestSpecsFromManifest, { createLatestSpecs } from './updateToLatestSpecsFromManifest'
import getSaveType from './getSaveType' import getSaveType from './getSaveType'
@@ -122,6 +123,7 @@ export default async function recursive (
: {} : {}
const targetDependenciesField = getSaveType(opts) const targetDependenciesField = getSaveType(opts)
const installOpts = Object.assign(opts, { const installOpts = Object.assign(opts, {
...getOptionsFromRootManifest(manifestsByPath[opts.lockfileDir ?? opts.dir]?.manifest ?? {}),
linkWorkspacePackagesDepth: opts.linkWorkspacePackages === 'deep' ? Infinity : opts.linkWorkspacePackages ? 0 : -1, linkWorkspacePackagesDepth: opts.linkWorkspacePackages === 'deep' ? Infinity : opts.linkWorkspacePackages ? 0 : -1,
ownLifecycleHooksStdio: 'pipe', ownLifecycleHooksStdio: 'pipe',
peer: opts.savePeer, peer: opts.savePeer,
@@ -356,6 +358,7 @@ export default async function recursive (
{ {
...installOpts, ...installOpts,
...localConfig, ...localConfig,
...getOptionsFromRootManifest(manifest),
bin: path.join(rootDir, 'node_modules', '.bin'), bin: path.join(rootDir, 'node_modules', '.bin'),
dir: rootDir, dir: rootDir,
hooks, hooks,

View File

@@ -17,6 +17,7 @@ import {
import pick from 'ramda/src/pick' import pick from 'ramda/src/pick'
import without from 'ramda/src/without' import without from 'ramda/src/without'
import renderHelp from 'render-help' import renderHelp from 'render-help'
import getOptionsFromRootManifest from './getOptionsFromRootManifest'
import getSaveType from './getSaveType' import getSaveType from './getSaveType'
import recursive from './recursive' import recursive from './recursive'
@@ -140,6 +141,7 @@ export async function handler (
| 'production' | 'production'
| 'rawLocalConfig' | 'rawLocalConfig'
| 'registries' | 'registries'
| 'rootProjectManifest'
| 'saveDev' | 'saveDev'
| 'saveOptional' | 'saveOptional'
| 'saveProd' | 'saveProd'
@@ -162,6 +164,7 @@ export async function handler (
} }
const store = await createOrConnectStoreController(opts) const store = await createOrConnectStoreController(opts)
const removeOpts = Object.assign(opts, { const removeOpts = Object.assign(opts, {
...getOptionsFromRootManifest(opts.rootProjectManifest ?? {}),
storeController: store.ctrl, storeController: store.ctrl,
storeDir: store.dir, storeDir: store.dir,
include, include,

View File

@@ -4,6 +4,7 @@ import { Config } from '@pnpm/config'
import { createOrConnectStoreController, CreateStoreControllerOptions } from '@pnpm/store-connection-manager' import { createOrConnectStoreController, CreateStoreControllerOptions } from '@pnpm/store-connection-manager'
import { mutateModules } from '@pnpm/core' import { mutateModules } from '@pnpm/core'
import renderHelp from 'render-help' import renderHelp from 'render-help'
import getOptionsFromRootManifest from './getOptionsFromRootManifest'
import { cliOptionsTypes, rcOptionsTypes } from './install' import { cliOptionsTypes, rcOptionsTypes } from './install'
import recursive from './recursive' import recursive from './recursive'
@@ -51,6 +52,7 @@ export async function handler (
| 'selectedProjectsGraph' | 'selectedProjectsGraph'
| 'rawLocalConfig' | 'rawLocalConfig'
| 'registries' | 'registries'
| 'rootProjectManifest'
| 'pnpmfile' | 'pnpmfile'
| 'workspaceDir' | 'workspaceDir'
> & { > & {
@@ -64,6 +66,7 @@ export async function handler (
} }
const store = await createOrConnectStoreController(opts) const store = await createOrConnectStoreController(opts)
const unlinkOpts = Object.assign(opts, { const unlinkOpts = Object.assign(opts, {
...getOptionsFromRootManifest(opts.rootProjectManifest ?? {}),
globalBin: opts.bin, globalBin: opts.bin,
storeController: store.ctrl, storeController: store.ctrl,
storeDir: store.dir, storeDir: store.dir,

View File

@@ -0,0 +1,21 @@
import getOptionsFromRootManifest from '@pnpm/plugin-commands-installation/lib/getOptionsFromRootManifest'
test('getOptionsFromRootManifest() should read "resolutions" field for compatibility with Yarn', () => {
const options = getOptionsFromRootManifest({
resolutions: {
foo: '1.0.0',
},
})
expect(options.overrides).toStrictEqual({ foo: '1.0.0' })
})
test('getOptionsFromRootManifest() should read "overrides" field', () => {
const options = getOptionsFromRootManifest({
pnpm: {
overrides: {
foo: '1.0.0',
},
},
})
expect(options.overrides).toStrictEqual({ foo: '1.0.0' })
})

2
pnpm-lock.yaml generated
View File

@@ -330,6 +330,7 @@ importers:
'@pnpm/global-bin-dir': workspace:3.0.0 '@pnpm/global-bin-dir': workspace:3.0.0
'@pnpm/pnpmfile': workspace:1.2.0 '@pnpm/pnpmfile': workspace:1.2.0
'@pnpm/prepare': workspace:0.0.28 '@pnpm/prepare': workspace:0.0.28
'@pnpm/read-project-manifest': workspace:2.0.7
'@pnpm/types': workspace:7.6.0 '@pnpm/types': workspace:7.6.0
'@types/ramda': 0.27.39 '@types/ramda': 0.27.39
'@types/which': ^2.0.0 '@types/which': ^2.0.0
@@ -347,6 +348,7 @@ importers:
'@pnpm/error': link:../error '@pnpm/error': link:../error
'@pnpm/global-bin-dir': link:../global-bin-dir '@pnpm/global-bin-dir': link:../global-bin-dir
'@pnpm/pnpmfile': link:../pnpmfile '@pnpm/pnpmfile': link:../pnpmfile
'@pnpm/read-project-manifest': link:../read-project-manifest
'@pnpm/types': link:../types '@pnpm/types': link:../types
'@zkochan/npm-conf': 2.0.2 '@zkochan/npm-conf': 2.0.2
camelcase: 6.2.1 camelcase: 6.2.1