diff --git a/.changeset/shy-brooms-feel.md b/.changeset/shy-brooms-feel.md new file mode 100644 index 0000000000..6be489bb6f --- /dev/null +++ b/.changeset/shy-brooms-feel.md @@ -0,0 +1,6 @@ +--- +"@pnpm/plugin-commands-config": patch +pnpm: patch +--- + +When both `pnpm-workspace.yaml` and `.npmrc` exist, `pnpm config set --location=project` now writes to `pnpm-workspace.yaml` (matching read priority) [#10072](https://github.com/pnpm/pnpm/issues/10072). diff --git a/config/plugin-commands-config/src/config.ts b/config/plugin-commands-config/src/config.ts index 3ea78ff6d0..71362035c2 100644 --- a/config/plugin-commands-config/src/config.ts +++ b/config/plugin-commands-config/src/config.ts @@ -54,7 +54,7 @@ export function help (): string { shortAlias: '-g', }, { - description: 'When set to "project", the .npmrc file at the nearest package.json will be used. If no .npmrc file is present in the directory, the setting will be written to a pnpm-workspace.yaml file.', + description: 'When set to "project", the pnpm-workspace.yaml file will be used if it exists. If only .npmrc exists, it will be used. If neither exists, a pnpm-workspace.yaml file will be created.', name: '--location ', }, { diff --git a/config/plugin-commands-config/src/configSet.ts b/config/plugin-commands-config/src/configSet.ts index 9cdf2b4066..e88a39b99c 100644 --- a/config/plugin-commands-config/src/configSet.ts +++ b/config/plugin-commands-config/src/configSet.ts @@ -1,5 +1,3 @@ -import fs from 'fs' -import path from 'path' import util from 'util' import { types } from '@pnpm/config' import { PnpmError } from '@pnpm/error' @@ -11,6 +9,7 @@ import kebabCase from 'lodash.kebabcase' import { readIniFile } from 'read-ini-file' import { writeIniFile } from 'write-ini-file' import { type ConfigCommandOptions } from './ConfigCommandOptions.js' +import { getConfigFilePath } from './getConfigFilePath.js' import { isStrictlyKebabCase } from './isStrictlyKebabCase.js' import { settingShouldFallBackToNpm } from './settingShouldFallBackToNpm.js' @@ -36,8 +35,17 @@ export async function configSet (opts: ConfigCommandOptions, key: string, valueP } throw new PnpmError('CONFIG_SET_AUTH_NON_STRING', `Cannot set ${key} to a non-string value (${JSON.stringify(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 { configPath, isWorkspaceYaml } = getConfigFilePath(opts) + + if (isWorkspaceYaml) { + key = camelCase(key) + await updateWorkspaceManifest(opts.workspaceDir ?? opts.dir, { + updatedFields: ({ + [key]: castField(value, kebabCase(key)), + }), + }) + } else { const settings = await safeReadIniFile(configPath) key = kebabCase(key) if (value == null) { @@ -47,14 +55,7 @@ export async function configSet (opts: ConfigCommandOptions, key: string, valueP settings[key] = value } await writeIniFile(configPath, settings) - return } - key = camelCase(key) - await updateWorkspaceManifest(opts.workspaceDir ?? opts.dir, { - updatedFields: ({ - [key]: castField(value, kebabCase(key)), - }), - }) } function castField (value: unknown, key: string) { diff --git a/config/plugin-commands-config/src/getConfigFilePath.ts b/config/plugin-commands-config/src/getConfigFilePath.ts new file mode 100644 index 0000000000..8532b5bd46 --- /dev/null +++ b/config/plugin-commands-config/src/getConfigFilePath.ts @@ -0,0 +1,42 @@ +import fs from 'fs' +import path from 'path' +import { type ConfigCommandOptions } from './ConfigCommandOptions.js' + +interface ConfigFilePathInfo { + configPath: string + isWorkspaceYaml: boolean +} + +/** + * Priority: pnpm-workspace.yaml > .npmrc > default to pnpm-workspace.yaml + */ +export function getConfigFilePath (opts: Pick): ConfigFilePathInfo { + if (opts.global) { + return { + configPath: path.join(opts.configDir, 'rc'), + isWorkspaceYaml: false, + } + } + + const workspaceYamlPath = path.join(opts.dir, 'pnpm-workspace.yaml') + if (fs.existsSync(workspaceYamlPath)) { + return { + configPath: workspaceYamlPath, + isWorkspaceYaml: true, + } + } + + const npmrcPath = path.join(opts.dir, '.npmrc') + if (fs.existsSync(npmrcPath)) { + return { + configPath: npmrcPath, + isWorkspaceYaml: false, + } + } + + // If neither exists, return pnpm-workspace.yaml + return { + configPath: workspaceYamlPath, + isWorkspaceYaml: true, + } +} diff --git a/config/plugin-commands-config/test/configSet.test.ts b/config/plugin-commands-config/test/configSet.test.ts index 4087df1320..a4d70cb679 100644 --- a/config/plugin-commands-config/test/configSet.test.ts +++ b/config/plugin-commands-config/test/configSet.test.ts @@ -290,3 +290,48 @@ test('config set with location=project and json=true', async () => { }, }) }) + +test('config set when both pnpm-workspace.yaml and .npmrc exist, pnpm-workspace.yaml has priority', async () => { + const tmp = tempDir() + const configDir = path.join(tmp, 'global-config') + fs.mkdirSync(configDir, { recursive: true }) + fs.writeFileSync(path.join(tmp, '.npmrc'), 'store-dir=~/store') + fs.writeFileSync(path.join(tmp, 'pnpm-workspace.yaml'), 'fetchRetries: 5') + + await config.handler({ + dir: process.cwd(), + cliOptions: {}, + configDir, + location: 'project', + rawConfig: {}, + }, ['set', 'fetch-timeout', '2000']) + + expect(readYamlFile(path.join(tmp, 'pnpm-workspace.yaml'))).toEqual({ + fetchRetries: 5, + fetchTimeout: 2000, + }) + expect(readIniFileSync(path.join(tmp, '.npmrc'))).toEqual({ + 'store-dir': '~/store', + }) +}) + +test('config set when only pnpm-workspace.yaml exists, writes to it', async () => { + const tmp = tempDir() + const configDir = path.join(tmp, 'global-config') + fs.mkdirSync(configDir, { recursive: true }) + fs.writeFileSync(path.join(tmp, 'pnpm-workspace.yaml'), 'fetchRetries: 5') + + await config.handler({ + dir: process.cwd(), + cliOptions: {}, + configDir, + location: 'project', + rawConfig: {}, + }, ['set', 'fetch-timeout', '3000']) + + expect(readYamlFile(path.join(tmp, 'pnpm-workspace.yaml'))).toEqual({ + fetchRetries: 5, + fetchTimeout: 3000, + }) + expect(fs.existsSync(path.join(tmp, '.npmrc'))).toBeFalsy() +})