diff --git a/.changeset/angry-moles-give.md b/.changeset/angry-moles-give.md new file mode 100644 index 00000000000..5589100b17e --- /dev/null +++ b/.changeset/angry-moles-give.md @@ -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. diff --git a/.changeset/chatty-toys-talk.md b/.changeset/chatty-toys-talk.md new file mode 100644 index 00000000000..01a8e2b8ee2 --- /dev/null +++ b/.changeset/chatty-toys-talk.md @@ -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`. diff --git a/.changeset/six-suits-float.md b/.changeset/six-suits-float.md new file mode 100644 index 00000000000..94fe222393e --- /dev/null +++ b/.changeset/six-suits-float.md @@ -0,0 +1,5 @@ +--- +"@pnpm/config": minor +--- + +Read the root project manifest and write it to the config object. diff --git a/packages/config/package.json b/packages/config/package.json index 267f8045d5d..585382d27fb 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -36,6 +36,7 @@ "@pnpm/error": "workspace:2.0.0", "@pnpm/global-bin-dir": "workspace:3.0.0", "@pnpm/pnpmfile": "workspace:1.2.0", + "@pnpm/read-project-manifest": "workspace:2.0.7", "@pnpm/types": "workspace:7.6.0", "@zkochan/npm-conf": "2.0.2", "camelcase": "^6.2.0", diff --git a/packages/config/src/Config.ts b/packages/config/src/Config.ts index 0cabb65c844..fb3565cc1c9 100644 --- a/packages/config/src/Config.ts +++ b/packages/config/src/Config.ts @@ -1,5 +1,6 @@ import { Project, + ProjectManifest, ProjectsGraph, Registries, } from '@pnpm/types' @@ -140,6 +141,7 @@ export interface Config { testPattern?: string[] changedFilesIgnorePattern?: string[] extendNodePath?: boolean + rootProjectManifest?: ProjectManifest } export interface ConfigWithDeprecatedSettings extends Config { diff --git a/packages/config/src/index.ts b/packages/config/src/index.ts index 73961e18a5c..eea1032a77d 100644 --- a/packages/config/src/index.ts +++ b/packages/config/src/index.ts @@ -5,6 +5,7 @@ import { LAYOUT_VERSION } from '@pnpm/constants' import PnpmError from '@pnpm/error' import globalBinDir from '@pnpm/global-bin-dir' import { requireHooks } from '@pnpm/pnpmfile' +import { safeReadProjectManifestOnly } from '@pnpm/read-project-manifest' import camelcase from 'camelcase' import loadNpmConf from '@zkochan/npm-conf' import npmTypes from '@zkochan/npm-conf/lib/types' @@ -483,6 +484,7 @@ export default async ( if (!pnpmConfig.ignorePnpmfile) { pnpmConfig.hooks = requireHooks(pnpmConfig.lockfileDir ?? pnpmConfig.dir, pnpmConfig) } + pnpmConfig.rootProjectManifest = await safeReadProjectManifestOnly(pnpmConfig.lockfileDir ?? pnpmConfig.dir) ?? undefined return { config: pnpmConfig, warnings } } diff --git a/packages/config/tsconfig.json b/packages/config/tsconfig.json index 1cfc6280d36..24d8b7d37ac 100644 --- a/packages/config/tsconfig.json +++ b/packages/config/tsconfig.json @@ -24,6 +24,9 @@ { "path": "../pnpmfile" }, + { + "path": "../read-project-manifest" + }, { "path": "../types" } diff --git a/packages/core/src/install/extendInstallOptions.ts b/packages/core/src/install/extendInstallOptions.ts index 6c831ae0532..e60bb9275ea 100644 --- a/packages/core/src/install/extendInstallOptions.ts +++ b/packages/core/src/install/extendInstallOptions.ts @@ -6,6 +6,7 @@ import normalizeRegistries, { DEFAULT_REGISTRIES } from '@pnpm/normalize-registr import { WorkspacePackages } from '@pnpm/resolver-base' import { StoreController } from '@pnpm/store-controller-types' import { + PackageExtension, ReadPackageHook, Registries, } from '@pnpm/types' @@ -44,8 +45,10 @@ export interface StrictInstallOptions { rawConfig: object verifyStoreIntegrity: boolean engineStrict: boolean + neverBuiltDependencies: string[] nodeExecPath?: string nodeVersion: string + packageExtensions: Record packageManager: { name: string version: string @@ -67,6 +70,7 @@ export interface StrictInstallOptions { unsafePerm: boolean registries: Registries tag: string + overrides: Record ownLifecycleHooksStdio: 'inherit' | 'pipe' workspacePackages: WorkspacePackages pruneStore: boolean @@ -120,9 +124,12 @@ const defaults = async (opts: InstallOptions) => { }, lockfileDir: opts.lockfileDir ?? opts.dir ?? process.cwd(), lockfileOnly: false, + neverBuiltDependencies: [] as string[], nodeVersion: process.version, + overrides: {}, ownLifecycleHooksStdio: 'inherit', ignorePackageManifest: false, + packageExtensions: {}, packageManager, preferFrozenLockfile: true, preferWorkspacePackages: false, diff --git a/packages/core/src/install/index.ts b/packages/core/src/install/index.ts index fc480bbc5e5..cdf07225570 100644 --- a/packages/core/src/install/index.ts +++ b/packages/core/src/install/index.ts @@ -166,19 +166,11 @@ export async function mutateModules ( // When running install/update on a subset of projects, the root project might not be included, // so reading its manifest explicitly here. 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({ readPackageHook: opts.hooks.readPackage, - overrides, + overrides: opts.overrides, lockfileDir: opts.lockfileDir, - packageExtensions, + packageExtensions: opts.packageExtensions, }) const ctx = await getContext(projects, opts) 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 && ( - !equals(ctx.wantedLockfile.overrides ?? {}, overrides ?? {}) || - !equals((ctx.wantedLockfile.neverBuiltDependencies ?? []).sort(), (neverBuiltDependencies ?? []).sort()) || + !equals(ctx.wantedLockfile.overrides ?? {}, opts.overrides ?? {}) || + !equals((ctx.wantedLockfile.neverBuiltDependencies ?? []).sort(), (opts.neverBuiltDependencies ?? []).sort()) || ctx.wantedLockfile.packageExtensionsChecksum !== packageExtensionsChecksum) || opts.fixLockfile if (needsFullResolution) { - ctx.wantedLockfile.overrides = overrides - ctx.wantedLockfile.neverBuiltDependencies = neverBuiltDependencies + ctx.wantedLockfile.overrides = opts.overrides + ctx.wantedLockfile.neverBuiltDependencies = opts.neverBuiltDependencies ctx.wantedLockfile.packageExtensionsChecksum = packageExtensionsChecksum } const frozenLockfile = opts.frozenLockfile || @@ -496,8 +488,6 @@ export async function mutateModules ( currentLockfileIsUpToDate: !ctx.existsWantedLockfile || ctx.currentLockfileIsUpToDate, makePartialCurrentLockfile, needsFullResolution, - neverBuiltDependencies, - overrides, pruneVirtualStore, updateLockfileMinorVersion: true, }) diff --git a/packages/core/test/install/lifecycleScripts.ts b/packages/core/test/install/lifecycleScripts.ts index 91556f7b04a..278ba70756d 100644 --- a/packages/core/test/install/lifecycleScripts.ts +++ b/packages/core/test/install/lifecycleScripts.ts @@ -446,9 +446,9 @@ test('scripts have access to unlisted bins when hoisting is used', async () => { test('selectively ignore scripts in some dependencies', async () => { const project = prepareEmpty() 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'], - await testDefaults({ fastUnpack: false }) + await testDefaults({ fastUnpack: false, neverBuiltDependencies }) ) 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 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-postinstall.js')).toBeFalsy() @@ -484,7 +484,6 @@ test('lockfile is updated if neverBuiltDependencies is changed', async () => { } const neverBuiltDependencies = ['pre-and-postinstall-scripts-example'] - manifest.pnpm = { neverBuiltDependencies } await mutateModules([ { buildIndex: 0, @@ -492,7 +491,7 @@ test('lockfile is updated if neverBuiltDependencies is changed', async () => { mutation: 'install', rootDir: process.cwd(), }, - ], await testDefaults()) + ], await testDefaults({ neverBuiltDependencies })) { const lockfile = await project.readLockfile() diff --git a/packages/core/test/install/multipleImporters.ts b/packages/core/test/install/multipleImporters.ts index f15c189d88d..c44cb733f77 100644 --- a/packages/core/test/install/multipleImporters.ts +++ b/packages/core/test/install/multipleImporters.ts @@ -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 () => { preparePackages([ - { - location: '.', - package: { - pnpm: { - overrides: { - 'dep-of-pkg-with-1-dep': 'workspace:*', - }, - }, - }, - }, { location: '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()) @@ -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 await mutateModules(importers, await testDefaults({ frozenLockfile: true, + overrides, workspacePackages, })) }) diff --git a/packages/core/test/install/overrides.ts b/packages/core/test/install/overrides.ts index fc4466f3c4f..a8d06681bbb 100644 --- a/packages/core/test/install/overrides.ts +++ b/packages/core/test/install/overrides.ts @@ -6,21 +6,21 @@ import { testDefaults, } 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() await addDistTag({ package: 'bar', version: '100.0.0', distTag: 'latest' }) await addDistTag({ package: 'foo', version: '100.0.0', distTag: 'latest' }) - const manifest = await addDependenciesToPackage({ - pnpm: { - overrides: { - 'foobarqar>foo': 'npm:qar@100.0.0', - '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', 'foobarqar@1.0.0'], await testDefaults()) + const overrides = { + 'foobarqar>foo': 'npm:qar@100.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 }) + ) { const lockfile = await project.readLockfile() @@ -44,12 +44,12 @@ test('versions are replaced with versions specified through pnpm.overrides field mutation: 'install', rootDir: process.cwd(), }, - ], { ...await testDefaults(), ignorePackageManifest: true }) + ], { ...await testDefaults(), ignorePackageManifest: true, overrides }) // 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 - manifest.pnpm!.overrides!['foobarqar'] = '1.0.1' + overrides['foobarqar'] = '1.0.1' await mutateModules([ { buildIndex: 0, @@ -57,7 +57,7 @@ test('versions are replaced with versions specified through pnpm.overrides field mutation: 'install', rootDir: process.cwd(), }, - ], await testDefaults()) + ], await testDefaults({ overrides })) { const lockfile = await project.readLockfile() @@ -81,7 +81,7 @@ test('versions are replaced with versions specified through pnpm.overrides field mutation: 'install', rootDir: process.cwd(), }, - ], await testDefaults({ frozenLockfile: true })) + ], await testDefaults({ frozenLockfile: true, overrides })) { 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) } - manifest.pnpm!.overrides!['bar@^100.0.0'] = '100.0.1' + overrides['bar@^100.0.0'] = '100.0.1' await expect( mutateModules([ { @@ -104,54 +104,10 @@ test('versions are replaced with versions specified through pnpm.overrides field mutation: 'install', rootDir: process.cwd(), }, - ], await testDefaults({ frozenLockfile: true })) + ], await testDefaults({ frozenLockfile: true, overrides })) ).rejects.toThrow( new PnpmError('FROZEN_LOCKFILE_WITH_OUTDATED_LOCKFILE', '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', - }) - } -}) diff --git a/packages/core/test/install/packageExtensions.ts b/packages/core/test/install/packageExtensions.ts index a8330865e00..f6f6bd8c41e 100644 --- a/packages/core/test/install/packageExtensions.ts +++ b/packages/core/test/install/packageExtensions.ts @@ -6,20 +6,21 @@ import { testDefaults, } 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 manifest = await addDependenciesToPackage({ - pnpm: { - packageExtensions: { - 'is-positive': { - dependencies: { - bar: '100.1.0', - }, - }, + const packageExtensions = { + 'is-positive': { + 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() @@ -36,7 +37,7 @@ test('manifests are extended with fields specified by pnpm.packageExtensions', a } // 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([ { buildIndex: 0, @@ -44,7 +45,7 @@ test('manifests are extended with fields specified by pnpm.packageExtensions', a mutation: 'install', rootDir: process.cwd(), }, - ], await testDefaults()) + ], await testDefaults({ packageExtensions })) { const lockfile = await project.readLockfile() @@ -68,7 +69,7 @@ test('manifests are extended with fields specified by pnpm.packageExtensions', a mutation: 'install', rootDir: process.cwd(), }, - ], await testDefaults({ frozenLockfile: true })) + ], await testDefaults({ frozenLockfile: true, packageExtensions })) { 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) } - manifest.pnpm!.packageExtensions!['is-positive'].dependencies!['bar'] = '100.0.1' + packageExtensions['is-positive'].dependencies!['bar'] = '100.0.1' await expect( mutateModules([ { @@ -93,7 +94,7 @@ test('manifests are extended with fields specified by pnpm.packageExtensions', a mutation: 'install', rootDir: process.cwd(), }, - ], await testDefaults({ frozenLockfile: true })) + ], await testDefaults({ frozenLockfile: true, packageExtensions })) ).rejects.toThrow( new PnpmError('FROZEN_LOCKFILE_WITH_OUTDATED_LOCKFILE', 'Cannot perform a frozen installation because the lockfile needs updates' diff --git a/packages/plugin-commands-installation/src/getOptionsFromRootManifest.ts b/packages/plugin-commands-installation/src/getOptionsFromRootManifest.ts new file mode 100644 index 00000000000..71f89bdd23f --- /dev/null +++ b/packages/plugin-commands-installation/src/getOptionsFromRootManifest.ts @@ -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, + } +} diff --git a/packages/plugin-commands-installation/src/import/index.ts b/packages/plugin-commands-installation/src/import/index.ts index 82b73018b05..65092869eed 100644 --- a/packages/plugin-commands-installation/src/import/index.ts +++ b/packages/plugin-commands-installation/src/import/index.ts @@ -21,6 +21,7 @@ import { parse as parseYarnLock } from '@yarnpkg/lockfile' import * as yarnCore from '@yarnpkg/core' import { parseSyml } from '@yarnpkg/parsers' import exists from 'path-exists' +import getOptionsFromRootManifest from '../getOptionsFromRootManifest' import recursive from '../recursive' import { yarnLockFileKeyNormalizer } from './yarnUtil' @@ -143,14 +144,16 @@ export async function handler ( } const store = await createOrConnectStoreController(opts) + const manifest = await readProjectManifestOnly(opts.dir) const installOpts = { ...opts, + ...getOptionsFromRootManifest(manifest), lockfileOnly: true, preferredVersions, storeController: store.ctrl, storeDir: store.dir, } - await install(await readProjectManifestOnly(opts.dir), installOpts) + await install(manifest, installOpts) } async function readYarnLockFile (dir: string) { @@ -279,4 +282,4 @@ function getYarnLockfileType ( return lockFileContents.includes('__metadata') ? YarnLockType.yarn2 : YarnLockType.yarn -} \ No newline at end of file +} diff --git a/packages/plugin-commands-installation/src/installDeps.ts b/packages/plugin-commands-installation/src/installDeps.ts index 71708460a39..12063a84f2c 100644 --- a/packages/plugin-commands-installation/src/installDeps.ts +++ b/packages/plugin-commands-installation/src/installDeps.ts @@ -18,6 +18,7 @@ import { import logger from '@pnpm/logger' import { sequenceGraph } from '@pnpm/sort-packages' import isSubdir from 'is-subdir' +import getOptionsFromRootManifest from './getOptionsFromRootManifest' import getPinnedVersion from './getPinnedVersion' import getSaveType from './getSaveType' import getNodeExecPath from './nodeExecPath' @@ -161,9 +162,18 @@ when running add/update with the --workspace option') 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 installOpts = { ...opts, + ...getOptionsFromRootManifest(manifest), forceHoistPattern, forcePublicHoistPattern, // 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 if (updateMatch != null) { params = matchDependencies(updateMatch, manifest, includeDirect) diff --git a/packages/plugin-commands-installation/src/link.ts b/packages/plugin-commands-installation/src/link.ts index f016aae8292..f9fe9f87a69 100644 --- a/packages/plugin-commands-installation/src/link.ts +++ b/packages/plugin-commands-installation/src/link.ts @@ -27,6 +27,7 @@ import pick from 'ramda/src/pick' import partition from 'ramda/src/partition' import renderHelp from 'render-help' import * as installCommand from './install' +import getOptionsFromRootManifest from './getOptionsFromRootManifest' import getSaveType from './getSaveType' const isWindows = process.platform === 'win32' || global['FAKE_WINDOWS'] @@ -140,6 +141,7 @@ export async function handler ( await install( await readProjectManifestOnly(dir, opts), { ...config, + ...getOptionsFromRootManifest(config.rootProjectManifest ?? {}), include: { dependencies: config.production !== false, devDependencies: config.dev !== false, diff --git a/packages/plugin-commands-installation/src/prune.ts b/packages/plugin-commands-installation/src/prune.ts index 7e457acbf69..c7c68d56d60 100644 --- a/packages/plugin-commands-installation/src/prune.ts +++ b/packages/plugin-commands-installation/src/prune.ts @@ -5,6 +5,7 @@ import { createOrConnectStoreController, CreateStoreControllerOptions } from '@p import { InstallOptions, mutateModules } from '@pnpm/core' import pick from 'ramda/src/pick' import renderHelp from 'render-help' +import getOptionsFromRootManifest from './getOptionsFromRootManifest' export const rcOptionsTypes = cliOptionsTypes @@ -44,19 +45,21 @@ export function help () { } export async function handler ( - opts: Pick & CreateStoreControllerOptions + opts: Pick & CreateStoreControllerOptions ) { const store = await createOrConnectStoreController(opts) + const manifest = await readProjectManifestOnly(process.cwd(), opts) return mutateModules([ { buildIndex: 0, - manifest: await readProjectManifestOnly(process.cwd(), opts), + manifest, mutation: 'install', pruneDirectDependencies: true, rootDir: process.cwd(), }, ], { ...opts, + ...getOptionsFromRootManifest(opts.rootProjectManifest ?? {}), include: { dependencies: opts.production !== false, devDependencies: opts.dev !== false, diff --git a/packages/plugin-commands-installation/src/recursive.ts b/packages/plugin-commands-installation/src/recursive.ts index 4b6aad9c143..caf9bd21751 100755 --- a/packages/plugin-commands-installation/src/recursive.ts +++ b/packages/plugin-commands-installation/src/recursive.ts @@ -34,6 +34,7 @@ import mem from 'mem' import pFilter from 'p-filter' import pLimit from 'p-limit' import readIniFile from 'read-ini-file' +import getOptionsFromRootManifest from './getOptionsFromRootManifest' import { createWorkspaceSpecs, updateToWorkspacePackagesFromManifest } from './updateWorkspaceDependencies' import updateToLatestSpecsFromManifest, { createLatestSpecs } from './updateToLatestSpecsFromManifest' import getSaveType from './getSaveType' @@ -122,6 +123,7 @@ export default async function recursive ( : {} const targetDependenciesField = getSaveType(opts) const installOpts = Object.assign(opts, { + ...getOptionsFromRootManifest(manifestsByPath[opts.lockfileDir ?? opts.dir]?.manifest ?? {}), linkWorkspacePackagesDepth: opts.linkWorkspacePackages === 'deep' ? Infinity : opts.linkWorkspacePackages ? 0 : -1, ownLifecycleHooksStdio: 'pipe', peer: opts.savePeer, @@ -356,6 +358,7 @@ export default async function recursive ( { ...installOpts, ...localConfig, + ...getOptionsFromRootManifest(manifest), bin: path.join(rootDir, 'node_modules', '.bin'), dir: rootDir, hooks, diff --git a/packages/plugin-commands-installation/src/remove.ts b/packages/plugin-commands-installation/src/remove.ts index 66796c5a6fc..72f7743f937 100644 --- a/packages/plugin-commands-installation/src/remove.ts +++ b/packages/plugin-commands-installation/src/remove.ts @@ -17,6 +17,7 @@ import { import pick from 'ramda/src/pick' import without from 'ramda/src/without' import renderHelp from 'render-help' +import getOptionsFromRootManifest from './getOptionsFromRootManifest' import getSaveType from './getSaveType' import recursive from './recursive' @@ -140,6 +141,7 @@ export async function handler ( | 'production' | 'rawLocalConfig' | 'registries' + | 'rootProjectManifest' | 'saveDev' | 'saveOptional' | 'saveProd' @@ -162,6 +164,7 @@ export async function handler ( } const store = await createOrConnectStoreController(opts) const removeOpts = Object.assign(opts, { + ...getOptionsFromRootManifest(opts.rootProjectManifest ?? {}), storeController: store.ctrl, storeDir: store.dir, include, diff --git a/packages/plugin-commands-installation/src/unlink.ts b/packages/plugin-commands-installation/src/unlink.ts index 9218d1b5fd7..0065f25aeff 100644 --- a/packages/plugin-commands-installation/src/unlink.ts +++ b/packages/plugin-commands-installation/src/unlink.ts @@ -4,6 +4,7 @@ import { Config } from '@pnpm/config' import { createOrConnectStoreController, CreateStoreControllerOptions } from '@pnpm/store-connection-manager' import { mutateModules } from '@pnpm/core' import renderHelp from 'render-help' +import getOptionsFromRootManifest from './getOptionsFromRootManifest' import { cliOptionsTypes, rcOptionsTypes } from './install' import recursive from './recursive' @@ -51,6 +52,7 @@ export async function handler ( | 'selectedProjectsGraph' | 'rawLocalConfig' | 'registries' + | 'rootProjectManifest' | 'pnpmfile' | 'workspaceDir' > & { @@ -64,6 +66,7 @@ export async function handler ( } const store = await createOrConnectStoreController(opts) const unlinkOpts = Object.assign(opts, { + ...getOptionsFromRootManifest(opts.rootProjectManifest ?? {}), globalBin: opts.bin, storeController: store.ctrl, storeDir: store.dir, diff --git a/packages/plugin-commands-installation/test/getOptionsFromRootManifest.test.ts b/packages/plugin-commands-installation/test/getOptionsFromRootManifest.test.ts new file mode 100644 index 00000000000..496b0471dd8 --- /dev/null +++ b/packages/plugin-commands-installation/test/getOptionsFromRootManifest.test.ts @@ -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' }) +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 33f7829f8ca..2883835da03 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -330,6 +330,7 @@ importers: '@pnpm/global-bin-dir': workspace:3.0.0 '@pnpm/pnpmfile': workspace:1.2.0 '@pnpm/prepare': workspace:0.0.28 + '@pnpm/read-project-manifest': workspace:2.0.7 '@pnpm/types': workspace:7.6.0 '@types/ramda': 0.27.39 '@types/which': ^2.0.0 @@ -347,6 +348,7 @@ importers: '@pnpm/error': link:../error '@pnpm/global-bin-dir': link:../global-bin-dir '@pnpm/pnpmfile': link:../pnpmfile + '@pnpm/read-project-manifest': link:../read-project-manifest '@pnpm/types': link:../types '@zkochan/npm-conf': 2.0.2 camelcase: 6.2.1