mirror of
https://github.com/pnpm/pnpm.git
synced 2025-12-24 23:58:07 -05:00
feat: setting config settings via pnpm-workspace.yaml (#9211)
Related discussion: https://github.com/orgs/pnpm/discussions/9037
This commit is contained in:
18
.changeset/public-hounds-live.md
Normal file
18
.changeset/public-hounds-live.md
Normal file
@@ -0,0 +1,18 @@
|
||||
---
|
||||
"@pnpm/plugin-commands-installation": minor
|
||||
"@pnpm/plugin-commands-patching": minor
|
||||
"@pnpm/config": minor
|
||||
"pnpm": minor
|
||||
---
|
||||
|
||||
`pnpm-workspace.yaml` can now hold all the settings that `.npmrc` accepts. The settings should use camelCase [#9211](https://github.com/pnpm/pnpm/pull/9211).
|
||||
|
||||
`pnpm-workspace.yaml` example:
|
||||
|
||||
```yaml
|
||||
verifyDepsBeforeRun: install
|
||||
optimisticRepeatInstall: true
|
||||
publicHoistPattern:
|
||||
- "*types*"
|
||||
- "!@types/react"
|
||||
```
|
||||
@@ -28,7 +28,20 @@ export type OptionsFromRootManifest = {
|
||||
|
||||
export function getOptionsFromRootManifest (manifestDir: string, manifest: ProjectManifest): OptionsFromRootManifest {
|
||||
const settings: OptionsFromRootManifest = getOptionsFromPnpmSettings(manifestDir, {
|
||||
...manifest.pnpm,
|
||||
...pick([
|
||||
'allowNonAppliedPatches',
|
||||
'allowedDeprecatedVersions',
|
||||
'configDependencies',
|
||||
'ignoredBuiltDependencies',
|
||||
'ignoredOptionalDependencies',
|
||||
'neverBuiltDependencies',
|
||||
'onlyBuiltDependencies',
|
||||
'onlyBuiltDependenciesFile',
|
||||
'overrides',
|
||||
'packageExtensions',
|
||||
'peerDependencyRules',
|
||||
'supportedArchitectures',
|
||||
], manifest.pnpm ?? {}),
|
||||
// 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
|
||||
@@ -41,20 +54,7 @@ export function getOptionsFromRootManifest (manifestDir: string, manifest: Proje
|
||||
}
|
||||
|
||||
export function getOptionsFromPnpmSettings (manifestDir: string, pnpmSettings: PnpmSettings, manifest?: ProjectManifest): OptionsFromRootManifest {
|
||||
const settings: OptionsFromRootManifest = pick([
|
||||
'allowNonAppliedPatches',
|
||||
'allowedDeprecatedVersions',
|
||||
'configDependencies',
|
||||
'ignoredBuiltDependencies',
|
||||
'ignoredOptionalDependencies',
|
||||
'neverBuiltDependencies',
|
||||
'onlyBuiltDependencies',
|
||||
'onlyBuiltDependenciesFile',
|
||||
'overrides',
|
||||
'packageExtensions',
|
||||
'peerDependencyRules',
|
||||
'supportedArchitectures',
|
||||
], pnpmSettings)
|
||||
const settings: OptionsFromRootManifest = { ...pnpmSettings }
|
||||
if (settings.overrides) {
|
||||
if (Object.keys(settings.overrides).length === 0) {
|
||||
delete settings.overrides
|
||||
|
||||
@@ -213,10 +213,13 @@ export async function getConfig (opts: {
|
||||
|
||||
const rcOptions = Object.keys(rcOptionsTypes)
|
||||
|
||||
const pnpmConfig: ConfigWithDeprecatedSettings = Object.fromEntries([
|
||||
...rcOptions.map((configKey) => [camelcase(configKey, { locale: 'en-US' }), npmConfig.get(configKey)]) as any, // eslint-disable-line
|
||||
...Object.entries(cliOptions).filter(([name, value]) => typeof value !== 'undefined').map(([name, value]) => [camelcase(name, { locale: 'en-US' }), value]),
|
||||
]) as unknown as ConfigWithDeprecatedSettings
|
||||
const configFromCliOpts = Object.fromEntries(Object.entries(cliOptions)
|
||||
.filter(([_, value]) => typeof value !== 'undefined')
|
||||
.map(([name, value]) => [camelcase(name, { locale: 'en-US' }), value])
|
||||
)
|
||||
const pnpmConfig: ConfigWithDeprecatedSettings = Object.assign(Object.fromEntries(
|
||||
rcOptions.map((configKey) => [camelcase(configKey, { locale: 'en-US' }), npmConfig.get(configKey)]) as any, // eslint-disable-line
|
||||
), configFromCliOpts) as unknown as ConfigWithDeprecatedSettings
|
||||
// Resolving the current working directory to its actual location is crucial.
|
||||
// This prevents potential inconsistencies in the future, especially when processing or mapping subdirectories.
|
||||
const cwd = fs.realpathSync(betterPathResolve(cliOptions.dir ?? npmConfig.localPrefix))
|
||||
@@ -499,9 +502,9 @@ export async function getConfig (opts: {
|
||||
const workspaceManifest = await readWorkspaceManifest(pnpmConfig.workspaceDir)
|
||||
|
||||
pnpmConfig.workspacePackagePatterns = cliOptions['workspace-packages'] as string[] ?? workspaceManifest?.packages ?? ['.']
|
||||
pnpmConfig.catalogs = getCatalogsFromWorkspaceManifest(workspaceManifest)
|
||||
if (workspaceManifest) {
|
||||
Object.assign(pnpmConfig, getOptionsFromPnpmSettings(pnpmConfig.workspaceDir, workspaceManifest, pnpmConfig.rootProjectManifest))
|
||||
Object.assign(pnpmConfig, getOptionsFromPnpmSettings(pnpmConfig.workspaceDir, workspaceManifest, pnpmConfig.rootProjectManifest), configFromCliOpts)
|
||||
pnpmConfig.catalogs = getCatalogsFromWorkspaceManifest(workspaceManifest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,6 +118,7 @@ export async function handler (opts: PatchCommitCommandOptions, params: string[]
|
||||
|
||||
return install.handler({
|
||||
...opts,
|
||||
patchedDependencies: rootProjectManifest!.pnpm!.patchedDependencies!,
|
||||
rootProjectManifest,
|
||||
rawLocalConfig: {
|
||||
...opts.rawLocalConfig,
|
||||
|
||||
@@ -281,6 +281,7 @@ export type InstallCommandOptions = Pick<Config,
|
||||
| 'lockfileOnly'
|
||||
| 'modulesDir'
|
||||
| 'nodeLinker'
|
||||
| 'patchedDependencies'
|
||||
| 'pnpmfile'
|
||||
| 'preferFrozenLockfile'
|
||||
| 'preferWorkspacePackages'
|
||||
|
||||
@@ -79,6 +79,7 @@ test('patch from configuration dependency is applied', async () => {
|
||||
...DEFAULT_OPTS,
|
||||
configDependencies: rootProjectManifest.pnpm!.configDependencies,
|
||||
dir: process.cwd(),
|
||||
patchedDependencies: rootProjectManifest.pnpm?.patchedDependencies,
|
||||
rootProjectManifest,
|
||||
rootProjectManifestDir: process.cwd(),
|
||||
}, ['@pnpm.e2e/foo@100.0.0'])
|
||||
|
||||
@@ -21,7 +21,7 @@ beforeEach(() => {
|
||||
|
||||
const f = fixtures(__dirname)
|
||||
|
||||
function addPatch (key: string, patchFixture: string, patchDest: string): void {
|
||||
function addPatch (key: string, patchFixture: string, patchDest: string): ProjectManifest {
|
||||
fs.mkdirSync(path.dirname(patchDest), { recursive: true })
|
||||
fs.copyFileSync(patchFixture, patchDest)
|
||||
let manifestText = fs.readFileSync('package.json', 'utf-8')
|
||||
@@ -35,6 +35,7 @@ function addPatch (key: string, patchFixture: string, patchDest: string): void {
|
||||
}
|
||||
manifestText = JSON.stringify(manifest, undefined, 2) + '\n'
|
||||
fs.writeFileSync('package.json', manifestText)
|
||||
return manifest
|
||||
}
|
||||
|
||||
const unpatchedModulesDir = (v: 1 | 2 | 3) => `node_modules/.pnpm/@pnpm.e2e+console-log@${v}.0.0/node_modules`
|
||||
@@ -54,12 +55,13 @@ test('bare package name as a patchedDependencies key should apply to all version
|
||||
}, ['@pnpm.e2e/depends-on-console-log@1.0.0'])
|
||||
fs.rmSync('pnpm-lock.yaml')
|
||||
|
||||
addPatch('@pnpm.e2e/console-log', patchFixture, 'patches/console-log.patch')
|
||||
const rootProjectManifest = addPatch('@pnpm.e2e/console-log', patchFixture, 'patches/console-log.patch')
|
||||
|
||||
await install.handler({
|
||||
...DEFAULT_OPTS,
|
||||
dir: process.cwd(),
|
||||
frozenLockfile: false,
|
||||
patchedDependencies: rootProjectManifest.pnpm?.patchedDependencies,
|
||||
})
|
||||
|
||||
{
|
||||
@@ -105,12 +107,13 @@ test('bare package name as a patchedDependencies key should apply to all possibl
|
||||
}, ['@pnpm.e2e/depends-on-console-log@1.0.0'])
|
||||
fs.rmSync('pnpm-lock.yaml')
|
||||
|
||||
addPatch('@pnpm.e2e/console-log', patchFixture, 'patches/console-log.patch')
|
||||
const rootProjectManifest = addPatch('@pnpm.e2e/console-log', patchFixture, 'patches/console-log.patch')
|
||||
|
||||
await install.handler({
|
||||
...DEFAULT_OPTS,
|
||||
dir: process.cwd(),
|
||||
frozenLockfile: false,
|
||||
patchedDependencies: rootProjectManifest.pnpm?.patchedDependencies,
|
||||
})
|
||||
|
||||
// the common patch does not apply to v1
|
||||
@@ -153,12 +156,13 @@ test('package name with version is prioritized over bare package name as keys of
|
||||
fs.rmSync('pnpm-lock.yaml')
|
||||
|
||||
addPatch('@pnpm.e2e/console-log', commonPatchFixture, 'patches/console-log.patch')
|
||||
addPatch('@pnpm.e2e/console-log@2.0.0', specializedPatchFixture, 'patches/console-log@2.0.0.patch')
|
||||
const rootProjectManifest = addPatch('@pnpm.e2e/console-log@2.0.0', specializedPatchFixture, 'patches/console-log@2.0.0.patch')
|
||||
|
||||
await install.handler({
|
||||
...DEFAULT_OPTS,
|
||||
dir: process.cwd(),
|
||||
frozenLockfile: false,
|
||||
patchedDependencies: rootProjectManifest.pnpm?.patchedDependencies,
|
||||
})
|
||||
|
||||
// the common patch applies to v1
|
||||
@@ -209,12 +213,13 @@ test('package name with version as a patchedDependencies key does not affect oth
|
||||
fs.rmSync('pnpm-lock.yaml')
|
||||
|
||||
addPatch('@pnpm.e2e/console-log@2.0.0', patchFixture2, 'patches/console-log@2.0.0.patch')
|
||||
addPatch('@pnpm.e2e/console-log@3.0.0', patchFixture3, 'patches/console-log@3.0.0.patch')
|
||||
const rootProjectManifest = addPatch('@pnpm.e2e/console-log@3.0.0', patchFixture3, 'patches/console-log@3.0.0.patch')
|
||||
|
||||
await install.handler({
|
||||
...DEFAULT_OPTS,
|
||||
dir: process.cwd(),
|
||||
frozenLockfile: false,
|
||||
patchedDependencies: rootProjectManifest.pnpm?.patchedDependencies,
|
||||
})
|
||||
|
||||
// v1 remains unpatched
|
||||
@@ -254,12 +259,13 @@ test('failure to apply patch with package name and version would cause throw an
|
||||
}, ['@pnpm.e2e/depends-on-console-log@1.0.0'])
|
||||
fs.rmSync('pnpm-lock.yaml')
|
||||
|
||||
addPatch('@pnpm.e2e/console-log@1.0.0', patchFixture, 'patches/console-log@1.0.0.patch')
|
||||
const rootProjectManifest = addPatch('@pnpm.e2e/console-log@1.0.0', patchFixture, 'patches/console-log@1.0.0.patch')
|
||||
|
||||
const promise = install.handler({
|
||||
...DEFAULT_OPTS,
|
||||
dir: process.cwd(),
|
||||
frozenLockfile: false,
|
||||
patchedDependencies: rootProjectManifest.pnpm?.patchedDependencies,
|
||||
})
|
||||
await expect(promise).rejects.toHaveProperty(['message'], expect.stringContaining('Could not apply patch'))
|
||||
await expect(promise).rejects.toHaveProperty(['message'], expect.stringContaining(path.resolve('patches/console-log@1.0.0.patch')))
|
||||
|
||||
11
pnpm/test/config.ts
Normal file
11
pnpm/test/config.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import fs from 'fs'
|
||||
import { WANTED_LOCKFILE } from '@pnpm/constants'
|
||||
import { prepare } from '@pnpm/prepare'
|
||||
import { execPnpmSync } from './utils'
|
||||
|
||||
test('read settings from pnpm-workspace.yaml', async () => {
|
||||
prepare()
|
||||
fs.writeFileSync('pnpm-workspace.yaml', 'useLockfile: false', 'utf8')
|
||||
expect(execPnpmSync(['install']).status).toBe(0)
|
||||
expect(fs.existsSync(WANTED_LOCKFILE)).toBeFalsy()
|
||||
})
|
||||
@@ -768,15 +768,16 @@ test('deploy with a shared lockfile should correctly handle packageExtensions',
|
||||
})
|
||||
|
||||
test('deploy with a shared lockfile should correctly handle patchedDependencies', async () => {
|
||||
const patchedDependencies = {
|
||||
'is-positive': '__patches__/is-positive.patch',
|
||||
}
|
||||
const preparedManifests: Record<string, ProjectManifest> = {
|
||||
root: {
|
||||
name: 'root',
|
||||
version: '0.0.0',
|
||||
private: true,
|
||||
pnpm: {
|
||||
patchedDependencies: {
|
||||
'is-positive': '__patches__/is-positive.patch',
|
||||
},
|
||||
patchedDependencies,
|
||||
},
|
||||
},
|
||||
'project-0': {
|
||||
@@ -818,6 +819,7 @@ test('deploy with a shared lockfile should correctly handle patchedDependencies'
|
||||
allProjectsGraph,
|
||||
selectedProjectsGraph: allProjectsGraph,
|
||||
dir: process.cwd(),
|
||||
patchedDependencies,
|
||||
recursive: true,
|
||||
lockfileDir: process.cwd(),
|
||||
workspaceDir: process.cwd(),
|
||||
@@ -828,6 +830,7 @@ test('deploy with a shared lockfile should correctly handle patchedDependencies'
|
||||
...DEFAULT_OPTS,
|
||||
allProjects,
|
||||
dir: process.cwd(),
|
||||
patchedDependencies,
|
||||
recursive: true,
|
||||
selectedProjectsGraph,
|
||||
sharedWorkspaceLockfile: true,
|
||||
|
||||
@@ -275,12 +275,14 @@ test('pnpm licenses should work with file protocol dependency', async () => {
|
||||
test('pnpm licenses should work with git protocol dep that have patches', async () => {
|
||||
const workspaceDir = tempDir()
|
||||
f.copy('with-git-protocol-patched-deps', workspaceDir)
|
||||
const patchedDependencies = JSON.parse(fs.readFileSync(path.join(workspaceDir, 'package.json'), 'utf8')).pnpm.patchedDependencies
|
||||
|
||||
const storeDir = path.join(workspaceDir, 'store')
|
||||
await install.handler({
|
||||
...DEFAULT_OPTS,
|
||||
dir: workspaceDir,
|
||||
frozenLockfile: true,
|
||||
patchedDependencies,
|
||||
pnpmHomeDir: '',
|
||||
storeDir,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user