feat: config set local should save settings in pnpm-workspace.yaml (#9316)

This commit is contained in:
Zoltan Kochan
2025-03-22 13:39:21 +01:00
committed by GitHub
parent e9e4c594e6
commit 9bcca9f760
15 changed files with 127 additions and 12 deletions

View File

@@ -0,0 +1,7 @@
---
"@pnpm/plugin-commands-config": minor
"@pnpm/config": minor
"pnpm": minor
---
`pnpm config get` and `list` also show settings set in `pnpm-workspace.yaml` files [#9316](https://github.com/pnpm/pnpm/pull/9316).

View File

@@ -0,0 +1,7 @@
---
"@pnpm/plugin-commands-config": minor
"@pnpm/config": minor
"pnpm": minor
---
`pnpm config set --location=project` saves the setting to a `pnpm-workspace.yaml` file if no `.npmrc` file is present in the directory [#9316](https://github.com/pnpm/pnpm/pull/9316).

View File

@@ -51,6 +51,7 @@
"can-write-to-dir": "catalog:",
"is-subdir": "catalog:",
"is-windows": "catalog:",
"lodash.kebabcase": "catalog:",
"normalize-registry-url": "catalog:",
"path-absolute": "catalog:",
"path-name": "catalog:",
@@ -67,6 +68,7 @@
"@pnpm/prepare": "workspace:*",
"@pnpm/test-fixtures": "workspace:*",
"@types/is-windows": "catalog:",
"@types/lodash.kebabcase": "catalog:",
"@types/ramda": "catalog:",
"@types/which": "catalog:",
"symlink-dir": "catalog:"

View File

@@ -12,6 +12,7 @@ import { createMatcher } from '@pnpm/matcher'
import betterPathResolve from 'better-path-resolve'
import camelcase from 'camelcase'
import isWindows from 'is-windows'
import kebabCase from 'lodash.kebabcase'
import normalizeRegistryUrl from 'normalize-registry-url'
import realpathMissing from 'realpath-missing'
import pathAbsolute from 'path-absolute'
@@ -489,7 +490,12 @@ export async function getConfig (opts: {
pnpmConfig.workspacePackagePatterns = cliOptions['workspace-packages'] as string[] ?? workspaceManifest?.packages ?? ['.']
if (workspaceManifest) {
Object.assign(pnpmConfig, getOptionsFromPnpmSettings(pnpmConfig.workspaceDir, workspaceManifest, pnpmConfig.rootProjectManifest), configFromCliOpts)
const newSettings = Object.assign(getOptionsFromPnpmSettings(pnpmConfig.workspaceDir, workspaceManifest, pnpmConfig.rootProjectManifest), configFromCliOpts)
for (const [key, value] of Object.entries(newSettings)) {
// @ts-expect-error
pnpmConfig[key] = value
pnpmConfig.rawConfig[kebabCase(key)] = value
}
pnpmConfig.catalogs = getCatalogsFromWorkspaceManifest(workspaceManifest)
}
}

View File

@@ -1039,4 +1039,5 @@ test('settings from pnpm-workspace.yaml are read', async () => {
})
expect(config.onlyBuiltDependencies).toStrictEqual(['foo'])
expect(config.rawConfig['only-built-dependencies']).toStrictEqual(['foo'])
})

View File

@@ -36,7 +36,10 @@
"@pnpm/error": "workspace:*",
"@pnpm/object.key-sorting": "workspace:*",
"@pnpm/run-npm": "workspace:*",
"@pnpm/workspace.manifest-writer": "workspace:*",
"camelcase": "catalog:",
"ini": "catalog:",
"lodash.kebabcase": "catalog:",
"read-ini-file": "catalog:",
"render-help": "catalog:",
"write-ini-file": "catalog:"
@@ -45,7 +48,9 @@
"@pnpm/logger": "workspace:*",
"@pnpm/plugin-commands-config": "workspace:*",
"@pnpm/prepare": "workspace:*",
"@types/ini": "catalog:"
"@types/ini": "catalog:",
"@types/lodash.kebabcase": "catalog:",
"read-yaml-file": "catalog:"
},
"engines": {
"node": ">=18.12"

View File

@@ -7,6 +7,7 @@ export type ConfigCommandOptions = Pick<Config,
| 'global'
| 'npmPath'
| 'rawConfig'
| 'workspaceDir'
> & {
json?: boolean
location?: 'global' | 'project'

View File

@@ -1,6 +1,7 @@
import kebabCase from 'lodash.kebabcase'
import { type ConfigCommandOptions } from './ConfigCommandOptions'
export function configGet (opts: ConfigCommandOptions, key: string): string {
const config = opts.rawConfig[key]
const config = opts.rawConfig[kebabCase(key)]
return Array.isArray(config) ? config.join(',') : String(config)
}

View File

@@ -1,12 +1,15 @@
import fs from 'fs'
import path from 'path'
import util from 'util'
import { runNpm } from '@pnpm/run-npm'
import { updateWorkspaceManifest } from '@pnpm/workspace.manifest-writer'
import camelCase from 'camelcase'
import kebabCase from 'lodash.kebabcase'
import { readIniFile } from 'read-ini-file'
import { writeIniFile } from 'write-ini-file'
import { type ConfigCommandOptions } from './ConfigCommandOptions'
export async function configSet (opts: ConfigCommandOptions, key: string, value: string | null): Promise<void> {
const configPath = opts.global ? path.join(opts.configDir, 'rc') : path.join(opts.dir, '.npmrc')
if (opts.global && settingShouldFallBackToNpm(key)) {
const _runNpm = runNpm.bind(null, opts.npmPath)
if (value == null) {
@@ -16,14 +19,23 @@ export async function configSet (opts: ConfigCommandOptions, key: string, value:
}
return
}
const settings = await safeReadIniFile(configPath)
if (value == null) {
if (settings[key] == null) return
delete settings[key]
} else {
settings[key] = value
if (opts.global === true || fs.existsSync(path.join(opts.dir, '.npmrc'))) {
const configPath = opts.global ? path.join(opts.configDir, 'rc') : path.join(opts.dir, '.npmrc')
const settings = await safeReadIniFile(configPath)
key = kebabCase(key)
if (value == null) {
if (settings[key] == null) return
delete settings[key]
} else {
settings[key] = value
}
await writeIniFile(configPath, settings)
return
}
await writeIniFile(configPath, settings)
key = camelCase(key)
await updateWorkspaceManifest(opts.workspaceDir ?? opts.dir, {
[key]: value,
})
}
function settingShouldFallBackToNpm (key: string): boolean {

View File

@@ -14,6 +14,20 @@ test('config get', async () => {
expect(configKey).toEqual('~/store')
})
test('config get works with camelCase', async () => {
const configKey = await config.handler({
dir: process.cwd(),
cliOptions: {},
configDir: process.cwd(),
global: true,
rawConfig: {
'store-dir': '~/store',
},
}, ['get', 'storeDir'])
expect(configKey).toEqual('~/store')
})
test('config get a boolean should return string format', async () => {
const configKey = await config.handler({
dir: process.cwd(),

View File

@@ -4,6 +4,7 @@ import { PnpmError } from '@pnpm/error'
import { tempDir } from '@pnpm/prepare'
import { config } from '@pnpm/plugin-commands-config'
import { readIniFileSync } from 'read-ini-file'
import { sync as readYamlFile } from 'read-yaml-file'
test('config set using the global option', async () => {
const tmp = tempDir()
@@ -37,7 +38,7 @@ test('config set using the location=global option', async () => {
configDir,
location: 'global',
rawConfig: {},
}, ['set', 'fetch-retries', '1'])
}, ['set', 'fetchRetries', '1'])
expect(readIniFileSync(path.join(configDir, 'rc'))).toEqual({
'store-dir': '~/store',
@@ -45,6 +46,24 @@ test('config set using the location=global option', async () => {
})
})
test('config set using the location=project option. The setting is written to pnpm-workspace.yaml, when .npmrc is not present', async () => {
const tmp = tempDir()
const configDir = path.join(tmp, 'global-config')
fs.mkdirSync(configDir, { recursive: true })
await config.handler({
dir: process.cwd(),
cliOptions: {},
configDir,
location: 'project',
rawConfig: {},
}, ['set', 'virtual-store-dir', '.pnpm'])
expect(readYamlFile(path.join(tmp, 'pnpm-workspace.yaml'))).toEqual({
virtualStoreDir: '.pnpm',
})
})
test('config set using the location=project option', async () => {
const tmp = tempDir()
const configDir = path.join(tmp, 'global-config')

View File

@@ -27,6 +27,9 @@
{
"path": "../../packages/logger"
},
{
"path": "../../workspace/manifest-writer"
},
{
"path": "../config"
}

View File

@@ -107,6 +107,7 @@
"jega",
"jhcg",
"jnbpamcxayl",
"kebabcase",
"kevva",
"keyfile",
"killcb",

34
pnpm-lock.yaml generated
View File

@@ -102,6 +102,9 @@ catalogs:
'@types/js-yaml':
specifier: ^4.0.9
version: 4.0.9
'@types/lodash.kebabcase':
specifier: 4.1.9
version: 4.1.9
'@types/lodash.throttle':
specifier: 4.1.7
version: 4.1.7
@@ -387,6 +390,9 @@ catalogs:
load-json-file:
specifier: ^6.2.0
version: 6.2.0
lodash.kebabcase:
specifier: ^4.1.1
version: 4.1.1
lodash.throttle:
specifier: 4.1.1
version: 4.1.1
@@ -1499,6 +1505,9 @@ importers:
is-windows:
specifier: 'catalog:'
version: 1.0.2
lodash.kebabcase:
specifier: 'catalog:'
version: 4.1.1
normalize-registry-url:
specifier: 'catalog:'
version: 2.0.0
@@ -1533,6 +1542,9 @@ importers:
'@types/is-windows':
specifier: 'catalog:'
version: 1.0.2
'@types/lodash.kebabcase':
specifier: 'catalog:'
version: 4.1.9
'@types/ramda':
specifier: 'catalog:'
version: 0.29.12
@@ -1680,9 +1692,18 @@ importers:
'@pnpm/run-npm':
specifier: workspace:*
version: link:../../exec/run-npm
'@pnpm/workspace.manifest-writer':
specifier: workspace:*
version: link:../../workspace/manifest-writer
camelcase:
specifier: 'catalog:'
version: 6.3.0
ini:
specifier: 'catalog:'
version: 4.1.1
lodash.kebabcase:
specifier: 'catalog:'
version: 4.1.1
read-ini-file:
specifier: 'catalog:'
version: 4.0.0
@@ -1705,6 +1726,12 @@ importers:
'@types/ini':
specifier: 'catalog:'
version: 1.3.31
'@types/lodash.kebabcase':
specifier: 'catalog:'
version: 4.1.9
read-yaml-file:
specifier: 'catalog:'
version: 2.1.0
crypto/hash:
dependencies:
@@ -9676,6 +9703,9 @@ packages:
'@types/keyv@3.1.4':
resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==}
'@types/lodash.kebabcase@4.1.9':
resolution: {integrity: sha512-kPrrmcVOhSsjAVRovN0lRfrbuidfg0wYsrQa5IYuoQO1fpHHGSme66oyiYA/5eQPVl8Z95OA3HG0+d2SvYC85w==}
'@types/lodash.throttle@4.1.7':
resolution: {integrity: sha512-znwGDpjCHQ4FpLLx19w4OXDqq8+OvREa05H89obtSyXyOFKL3dDjCslsmfBz0T2FU8dmf5Wx1QvogbINiGIu9g==}
@@ -16704,6 +16734,10 @@ snapshots:
dependencies:
'@types/node': 22.13.10
'@types/lodash.kebabcase@4.1.9':
dependencies:
'@types/lodash': 4.17.16
'@types/lodash.throttle@4.1.7':
dependencies:
'@types/lodash': 4.17.16

View File

@@ -89,6 +89,7 @@ catalog:
"@types/is-gzip": 2.0.0
"@types/is-windows": ^1.0.2
"@types/js-yaml": ^4.0.9
"@types/lodash.kebabcase": 4.1.9
"@types/lodash.throttle": 4.1.7
"@types/micromatch": ^4.0.9
"@types/node": ^18.19.34
@@ -184,6 +185,7 @@ catalog:
jest-diff: ^29.7.0
json5: ^2.2.3
load-json-file: ^6.2.0
lodash.kebabcase: ^4.1.1
lodash.throttle: 4.1.1
loud-rejection: ^2.2.0
lru-cache: ^10.4.3