@pnpm/config.commands 1000.2.10 → 1100.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,7 @@
1
- import type { Config } from '@pnpm/config.reader';
2
- export type ConfigCommandOptions = Pick<Config, 'configDir' | 'cliOptions' | 'dir' | 'global' | 'npmPath' | 'rawConfig' | 'workspaceDir'> & {
1
+ import type { Config, ConfigContext } from '@pnpm/config.reader';
2
+ export type ConfigCommandOptions = Pick<Config, 'configDir' | 'dir' | 'global' | 'authConfig' | 'workspaceDir'> & Pick<ConfigContext, 'cliOptions'> & {
3
+ _config: Config;
4
+ _context: ConfigContext;
3
5
  json?: boolean;
4
6
  location?: 'global' | 'project';
5
7
  };
package/lib/configGet.js CHANGED
@@ -1,54 +1,61 @@
1
- import path from 'node:path';
2
- import { types } from '@pnpm/config.reader';
3
- import { runNpm } from '@pnpm/exec.run-npm';
1
+ import { isIniConfigKey, types } from '@pnpm/config.reader';
4
2
  import { getObjectValueByPropertyPath } from '@pnpm/object.property-path';
5
- import { isCamelCase, isStrictlyKebabCase } from '@pnpm/text.naming-cases';
3
+ import { isCamelCase } from '@pnpm/text.naming-cases';
4
+ import camelcase from 'camelcase';
6
5
  import kebabCase from 'lodash.kebabcase';
6
+ import { configToRecord } from './configToRecord.js';
7
7
  import { parseConfigPropertyPath } from './parseConfigPropertyPath.js';
8
- import { processConfig } from './processConfig.js';
9
- import { settingShouldFallBackToNpm } from './settingShouldFallBackToNpm.js';
10
8
  export function configGet(opts, key) {
11
9
  const isScopedKey = key.startsWith('@');
12
- // Exclude scoped keys from npm fallback because they are pnpm-native config
13
- // that can be read directly from rawConfig (e.g., '@scope:registry')
14
- if (opts.global && settingShouldFallBackToNpm(key) && !isScopedKey) {
15
- const { status: exitCode } = runNpm(opts.npmPath, ['config', 'get', key], {
16
- location: 'user',
17
- userConfigPath: path.join(opts.configDir, 'rc'),
18
- });
19
- return { output: '', exitCode: exitCode ?? 0 };
20
- }
21
- const configResult = getRcConfig(opts.rawConfig, key, isScopedKey) ?? getConfigByPropertyPath(opts.rawConfig, key);
10
+ const configResult = lookupConfig(opts, key, isScopedKey) ?? (isPropertyPath(key) ? lookupByPropertyPath(opts, key) : { value: undefined });
22
11
  const output = displayConfig(configResult?.value, opts);
23
12
  return { output, exitCode: 0 };
24
13
  }
25
- function getRcConfig(rawConfig, key, isScopedKey) {
14
+ function lookupConfig(opts, key, isScopedKey) {
26
15
  if (isScopedKey) {
27
- const value = rawConfig[key];
28
- return { value };
16
+ return { value: opts.authConfig[key] };
17
+ }
18
+ const kebabKey = isCamelCase(key) ? kebabCase(key) : key;
19
+ // Resolve typed keys from Config — check explicitly set values first,
20
+ // then fall back to authConfig (for keys like registry set in .npmrc)
21
+ if (Object.hasOwn(types, kebabKey)) {
22
+ const camelKey = camelcase(kebabKey, { locale: 'en-US' });
23
+ const explicit = opts._context.explicitlySetKeys;
24
+ if (!explicit || explicit.has(camelKey)) {
25
+ return { value: opts._config[camelKey] };
26
+ }
27
+ // Fall back to authConfig for INI keys (registry, ca, etc.)
28
+ if (kebabKey in opts.authConfig) {
29
+ return { value: opts.authConfig[kebabKey] };
30
+ }
31
+ return { value: undefined };
29
32
  }
30
- const rcKey = isCamelCase(key) ? kebabCase(key) : key;
31
- if (Object.hasOwn(types, rcKey)) {
32
- const value = rawConfig[rcKey];
33
- return { value };
33
+ // Auth-specific INI keys (//host:_authToken, _auth, etc.) from authConfig
34
+ if (isIniConfigKey(key)) {
35
+ return { value: opts.authConfig[key] };
34
36
  }
35
- if (isStrictlyKebabCase(key)) {
36
- const value = rawConfig[key];
37
- return { value };
37
+ // For keys not in types (e.g., package-extensions), look up via configToRecord
38
+ // which excludes internal/sensitive fields.
39
+ const camelKey = camelcase(key, { locale: 'en-US' });
40
+ const record = configToRecord(opts._config, opts._context.explicitlySetKeys);
41
+ if (Object.hasOwn(record, camelKey)) {
42
+ return { value: record[camelKey] };
38
43
  }
39
44
  return undefined;
40
45
  }
41
- function getConfigByPropertyPath(rawConfig, propertyPath) {
46
+ function lookupByPropertyPath(opts, propertyPath) {
42
47
  const parsedPropertyPath = Array.from(parseConfigPropertyPath(propertyPath));
43
48
  if (parsedPropertyPath.length === 0) {
44
- return {
45
- value: processConfig(rawConfig),
46
- };
49
+ return { value: configToRecord(opts._config, opts._context.explicitlySetKeys) };
47
50
  }
51
+ const record = configToRecord(opts._config, opts._context.explicitlySetKeys);
48
52
  return {
49
- value: getObjectValueByPropertyPath(rawConfig, parsedPropertyPath),
53
+ value: getObjectValueByPropertyPath(record, parsedPropertyPath),
50
54
  };
51
55
  }
56
+ function isPropertyPath(key) {
57
+ return key === '' || key.includes('.') || key.includes('[');
58
+ }
52
59
  function displayConfig(config, opts) {
53
60
  if (Boolean(opts.json) || Array.isArray(config)) {
54
61
  return JSON.stringify(config, undefined, 2);
@@ -1,3 +1,2 @@
1
1
  import type { ConfigCommandOptions } from './ConfigCommandOptions.js';
2
- export type ConfigListOptions = Pick<ConfigCommandOptions, 'rawConfig'>;
3
- export declare function configList(opts: ConfigListOptions): Promise<string>;
2
+ export declare function configList(opts: ConfigCommandOptions): Promise<string>;
package/lib/configList.js CHANGED
@@ -1,6 +1,5 @@
1
- import { processConfig } from './processConfig.js';
1
+ import { configToRecord } from './configToRecord.js';
2
2
  export async function configList(opts) {
3
- const processedConfig = processConfig(opts.rawConfig);
4
- return JSON.stringify(processedConfig, undefined, 2);
3
+ return JSON.stringify(configToRecord(opts._config, opts._context.explicitlySetKeys), undefined, 2);
5
4
  }
6
5
  //# sourceMappingURL=configList.js.map
package/lib/configSet.js CHANGED
@@ -1,9 +1,8 @@
1
1
  import path from 'node:path';
2
2
  import util from 'node:util';
3
- import { isConfigFileKey, types } from '@pnpm/config.reader';
3
+ import { isConfigFileKey, isIniConfigKey, types } from '@pnpm/config.reader';
4
4
  import { GLOBAL_CONFIG_YAML_FILENAME, WORKSPACE_MANIFEST_FILENAME } from '@pnpm/constants';
5
5
  import { PnpmError } from '@pnpm/error';
6
- import { runNpm } from '@pnpm/exec.run-npm';
7
6
  import { parsePropertyPath } from '@pnpm/object.property-path';
8
7
  import { isCamelCase, isStrictlyKebabCase } from '@pnpm/text.naming-cases';
9
8
  import { updateWorkspaceManifest } from '@pnpm/workspace.workspace-manifest-writer';
@@ -12,49 +11,34 @@ import kebabCase from 'lodash.kebabcase';
12
11
  import { readIniFile } from 'read-ini-file';
13
12
  import { writeIniFile } from 'write-ini-file';
14
13
  import { getConfigFileInfo } from './getConfigFileInfo.js';
15
- import { settingShouldFallBackToNpm } from './settingShouldFallBackToNpm.js';
16
14
  export async function configSet(opts, key, valueParam) {
17
- let shouldFallbackToNpm = settingShouldFallBackToNpm(key);
18
- if (!shouldFallbackToNpm) {
15
+ let isAuthSetting = isIniConfigKey(key);
16
+ if (!isAuthSetting) {
19
17
  key = validateSimpleKey(key);
20
- shouldFallbackToNpm = settingShouldFallBackToNpm(key);
18
+ isAuthSetting = isIniConfigKey(key);
21
19
  }
22
20
  let value = valueParam;
23
21
  if (valueParam != null && opts.json) {
24
22
  value = JSON.parse(valueParam);
25
23
  }
26
- if (shouldFallbackToNpm) {
27
- if (opts.global) {
28
- const configPath = path.join(opts.configDir, 'rc');
29
- const runNpmOpts = {
30
- location: 'user',
31
- userConfigPath: configPath,
32
- };
33
- const _runNpm = runNpm.bind(null, opts.npmPath);
34
- if (value == null) {
35
- _runNpm(['config', 'delete', key], runNpmOpts);
36
- return;
37
- }
38
- if (typeof value === 'string') {
39
- _runNpm(['config', 'set', `${key}=${value}`], runNpmOpts);
40
- return;
41
- }
24
+ if (isAuthSetting) {
25
+ const configPath = opts.global
26
+ ? path.join(opts.configDir, 'auth.ini')
27
+ : path.join(opts.dir, '.npmrc');
28
+ if (value != null && typeof value !== 'string' && isStringOnlyIniKey(key)) {
42
29
  throw new PnpmError('CONFIG_SET_AUTH_NON_STRING', `Cannot set ${key} to a non-string value (${JSON.stringify(value)})`);
43
30
  }
31
+ const settings = await safeReadIniFile(configPath);
32
+ if (value == null) {
33
+ if (settings[key] == null)
34
+ return;
35
+ delete settings[key];
36
+ }
44
37
  else {
45
- const configPath = path.join(opts.dir, '.npmrc');
46
- const settings = await safeReadIniFile(configPath);
47
- if (value == null) {
48
- if (settings[key] == null)
49
- return;
50
- delete settings[key];
51
- }
52
- else {
53
- settings[key] = value;
54
- }
55
- await writeIniFile(configPath, settings);
56
- return;
38
+ settings[key] = value;
57
39
  }
40
+ await writeIniFile(configPath, settings);
41
+ return;
58
42
  }
59
43
  const { configDir, configFileName } = getConfigFileInfo(key, opts);
60
44
  const configPath = path.join(configDir, configFileName);
@@ -73,7 +57,7 @@ export async function configSet(opts, key, valueParam) {
73
57
  });
74
58
  break;
75
59
  }
76
- case 'rc':
60
+ case 'auth.ini':
77
61
  case '.npmrc': {
78
62
  const settings = await safeReadIniFile(configPath);
79
63
  key = validateIniConfigKey(key);
@@ -197,6 +181,16 @@ function validateWorkspaceKey(key) {
197
181
  throw new ConfigSetUnsupportedWorkspaceKeyError(key);
198
182
  return key;
199
183
  }
184
+ const STRING_ONLY_INI_KEYS = ['_auth', '_authToken', '_password', 'username', 'registry'];
185
+ function isStringOnlyIniKey(key) {
186
+ if (STRING_ONLY_INI_KEYS.includes(key))
187
+ return true;
188
+ if (key.startsWith('@'))
189
+ return true;
190
+ if (key.startsWith('//'))
191
+ return true;
192
+ return false;
193
+ }
200
194
  async function safeReadIniFile(configPath) {
201
195
  try {
202
196
  return await readIniFile(configPath);
@@ -0,0 +1,10 @@
1
+ import { type Config } from '@pnpm/config.reader';
2
+ /**
3
+ * Convert a Config object to a camelCase record for display.
4
+ * Only includes explicitly set values (from CLI, env vars, or workspace yaml),
5
+ * not default values. Auth/registry keys from authConfig are always included.
6
+ *
7
+ * Accepts a clean Config object (without ConfigContext fields mixed in),
8
+ * so no INTERNAL_CONFIG_KEYS exclusion list is needed.
9
+ */
10
+ export declare function configToRecord(config: Config, explicitlySetKeys: Set<string>): Record<string, unknown>;
@@ -0,0 +1,49 @@
1
+ import { types } from '@pnpm/config.reader';
2
+ import { sortDirectKeys } from '@pnpm/object.key-sorting';
3
+ import camelcase from 'camelcase';
4
+ import { censorProtectedSettings } from './protectedSettings.js';
5
+ // Auth-related Config fields that are internal objects, not user settings.
6
+ const NON_SETTING_CONFIG_KEYS = new Set([
7
+ 'authConfig', 'configByUri',
8
+ ]);
9
+ /**
10
+ * Convert a Config object to a camelCase record for display.
11
+ * Only includes explicitly set values (from CLI, env vars, or workspace yaml),
12
+ * not default values. Auth/registry keys from authConfig are always included.
13
+ *
14
+ * Accepts a clean Config object (without ConfigContext fields mixed in),
15
+ * so no INTERNAL_CONFIG_KEYS exclusion list is needed.
16
+ */
17
+ export function configToRecord(config, explicitlySetKeys) {
18
+ const result = {};
19
+ // Add typed settings (only explicitly set ones if tracking is available)
20
+ for (const kebabKey of Object.keys(types)) {
21
+ const camelKey = camelcase(kebabKey, { locale: 'en-US' });
22
+ if (!explicitlySetKeys.has(camelKey))
23
+ continue;
24
+ const value = config[camelKey];
25
+ if (value !== undefined) {
26
+ result[camelKey] = value;
27
+ }
28
+ }
29
+ // Add non-types config properties (e.g., packageExtensions, overrides)
30
+ for (const [key, value] of Object.entries(config)) {
31
+ if (value === undefined || NON_SETTING_CONFIG_KEYS.has(key))
32
+ continue;
33
+ if (!(key in result) && explicitlySetKeys.has(key)) {
34
+ result[key] = value;
35
+ }
36
+ }
37
+ // Add auth/registry keys (scoped keys, auth tokens) — keep original casing
38
+ for (const [key, value] of Object.entries(config.authConfig)) {
39
+ if (!(key in result)) {
40
+ result[key] = value;
41
+ }
42
+ }
43
+ // Always include user-agent for debugging connectivity issues
44
+ if (config.userAgent) {
45
+ result.userAgent = config.userAgent;
46
+ }
47
+ return censorProtectedSettings(sortDirectKeys(result));
48
+ }
49
+ //# sourceMappingURL=configToRecord.js.map
@@ -1,6 +1,6 @@
1
1
  import { GLOBAL_CONFIG_YAML_FILENAME, WORKSPACE_MANIFEST_FILENAME } from '@pnpm/constants';
2
2
  import type { ConfigCommandOptions } from './ConfigCommandOptions.js';
3
- export type ConfigFileName = 'rc' | '.npmrc' | typeof GLOBAL_CONFIG_YAML_FILENAME | typeof WORKSPACE_MANIFEST_FILENAME;
3
+ export type ConfigFileName = 'auth.ini' | '.npmrc' | typeof GLOBAL_CONFIG_YAML_FILENAME | typeof WORKSPACE_MANIFEST_FILENAME;
4
4
  export interface ConfigFilePathInfo {
5
5
  configDir: string;
6
6
  configFileName: ConfigFileName;
@@ -9,7 +9,7 @@ export function getConfigFileInfo(key, opts) {
9
9
  // but considering the settings are now clearly divided into 2 separate categories, it should no longer be relevant.
10
10
  // TODO: Auth, network, and proxy settings should belong only to INI files.
11
11
  // Add more settings to `isIniConfigKey` to make it complete.
12
- const configFileName = opts.global ? 'rc' : '.npmrc';
12
+ const configFileName = opts.global ? 'auth.ini' : '.npmrc';
13
13
  return { configDir, configFileName };
14
14
  }
15
15
  else {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Just like {@link parsePropertyPath} but the first element may be converted into kebab-case
3
- * if it's part of {@link types}.
2
+ * Just like {@link parsePropertyPath} but the first element is converted to camelCase
3
+ * to match the camelCase keys produced by {@link configToRecord}.
4
4
  */
5
5
  export declare function parseConfigPropertyPath(propertyPath: string): Generator<string | number, void, void>;
@@ -1,28 +1,15 @@
1
- import { types } from '@pnpm/config.reader';
2
1
  import { parsePropertyPath } from '@pnpm/object.property-path';
3
- import kebabCase from 'lodash.kebabcase';
2
+ import camelcase from 'camelcase';
4
3
  /**
5
- * Just like {@link parsePropertyPath} but the first element may be converted into kebab-case
6
- * if it's part of {@link types}.
4
+ * Just like {@link parsePropertyPath} but the first element is converted to camelCase
5
+ * to match the camelCase keys produced by {@link configToRecord}.
7
6
  */
8
7
  export function* parseConfigPropertyPath(propertyPath) {
9
8
  const iter = parsePropertyPath(propertyPath);
10
9
  const first = iter.next();
11
10
  if (first.done)
12
11
  return;
13
- yield normalizeTopLevelConfigName(first.value);
12
+ yield typeof first.value === 'number' ? first.value : camelcase(first.value, { locale: 'en-US' });
14
13
  yield* iter;
15
14
  }
16
- /**
17
- * Turn a top-level config name into kebab-case if it's part of {@link types}.
18
- * Otherwise, return the string as-is.
19
- */
20
- function normalizeTopLevelConfigName(configName) {
21
- if (typeof configName === 'number')
22
- return configName.toString();
23
- const kebabKey = kebabCase(configName);
24
- if (Object.hasOwn(types, kebabKey))
25
- return kebabKey;
26
- return configName;
27
- }
28
15
  //# sourceMappingURL=parseConfigPropertyPath.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pnpm/config.commands",
3
- "version": "1000.2.10",
3
+ "version": "1100.0.0",
4
4
  "description": "Commands for reading and writing settings to/from config files",
5
5
  "keywords": [
6
6
  "pnpm",
@@ -32,27 +32,26 @@
32
32
  "render-help": "^2.0.0",
33
33
  "write-ini-file": "5.0.0",
34
34
  "write-yaml-file": "^6.0.0",
35
- "@pnpm/cli.utils": "1001.2.8",
36
- "@pnpm/config.reader": "1004.4.2",
37
- "@pnpm/error": "1000.0.5",
38
- "@pnpm/constants": "1001.3.1",
39
- "@pnpm/exec.run-npm": "1000.0.0",
40
- "@pnpm/object.key-sorting": "1000.0.1",
41
- "@pnpm/object.property-path": "1000.0.1",
42
- "@pnpm/text.naming-cases": "1100.0.0-0",
43
- "@pnpm/workspace.workspace-manifest-writer": "1001.0.3"
35
+ "@pnpm/constants": "1100.0.0",
36
+ "@pnpm/error": "1100.0.0",
37
+ "@pnpm/cli.utils": "1100.0.0",
38
+ "@pnpm/object.key-sorting": "1100.0.0",
39
+ "@pnpm/object.property-path": "1100.0.0",
40
+ "@pnpm/text.naming-cases": "1100.0.0",
41
+ "@pnpm/config.reader": "1100.0.0",
42
+ "@pnpm/workspace.workspace-manifest-writer": "1100.0.0"
44
43
  },
45
44
  "peerDependencies": {
46
45
  "@pnpm/logger": ">=1001.0.0 <1002.0.0"
47
46
  },
48
47
  "devDependencies": {
49
- "@jest/globals": "30.0.5",
50
- "@types/ini": "1.3.31",
48
+ "@jest/globals": "30.3.0",
49
+ "@types/ini": "4.1.1",
51
50
  "@types/lodash.kebabcase": "4.1.9",
52
51
  "read-yaml-file": "^3.0.0",
53
- "@pnpm/config.commands": "1000.2.10",
54
- "@pnpm/logger": "1001.0.1",
55
- "@pnpm/prepare": "1000.0.4"
52
+ "@pnpm/logger": "1100.0.0",
53
+ "@pnpm/prepare": "1100.0.0",
54
+ "@pnpm/config.commands": "1100.0.0"
56
55
  },
57
56
  "engines": {
58
57
  "node": ">=22.13"
@@ -62,8 +61,8 @@
62
61
  },
63
62
  "scripts": {
64
63
  "lint": "eslint \"src/**/*.ts\" \"test/**/*.ts\"",
65
- "_test": "cross-env NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules --disable-warning=ExperimentalWarning --disable-warning=DEP0169\" jest",
66
- "test": "pnpm run compile && pnpm run _test",
67
- "compile": "tsgo --build && pnpm run lint --fix"
64
+ "test": "pn compile && pn .test",
65
+ "compile": "tsgo --build && pn lint --fix",
66
+ ".test": "cross-env NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules --disable-warning=ExperimentalWarning --disable-warning=DEP0169\" jest"
68
67
  }
69
68
  }
@@ -1,4 +0,0 @@
1
- export interface ProcessConfigOptions {
2
- json?: boolean;
3
- }
4
- export declare function processConfig(rawConfig: Record<string, unknown>): Record<string, unknown>;
@@ -1,16 +0,0 @@
1
- import { sortDirectKeys } from '@pnpm/object.key-sorting';
2
- import camelcase from 'camelcase';
3
- import { censorProtectedSettings } from './protectedSettings.js';
4
- const shouldChangeCase = (key) => key[0] !== '@' && !key.startsWith('//');
5
- function camelCaseConfig(rawConfig) {
6
- const result = {};
7
- for (const key in rawConfig) {
8
- const targetKey = shouldChangeCase(key) ? camelcase(key) : key;
9
- result[targetKey] = rawConfig[key];
10
- }
11
- return result;
12
- }
13
- export function processConfig(rawConfig) {
14
- return camelCaseConfig(censorProtectedSettings(sortDirectKeys(rawConfig)));
15
- }
16
- //# sourceMappingURL=processConfig.js.map
@@ -1 +0,0 @@
1
- export declare function settingShouldFallBackToNpm(key: string): boolean;
@@ -1,9 +0,0 @@
1
- // NOTE: The logic may be duplicated with `isIniConfigKey` from `@pnpm/config.reader`,
2
- // but we have not the time to refactor it right now.
3
- // TODO: Refactor it when we have the time.
4
- export function settingShouldFallBackToNpm(key) {
5
- return (['registry', '_auth', '_authToken', 'username', '_password'].includes(key) ||
6
- key[0] === '@' ||
7
- key.startsWith('//'));
8
- }
9
- //# sourceMappingURL=settingShouldFallBackToNpm.js.map