Skip to content

Commit

Permalink
feat: implement the config commands (#5829)
Browse files Browse the repository at this point in the history
close #5621
  • Loading branch information
zkochan committed Dec 24, 2022
1 parent 4700b09 commit 841f52e
Show file tree
Hide file tree
Showing 29 changed files with 492 additions and 25 deletions.
16 changes: 16 additions & 0 deletions .changeset/perfect-squids-count.md
@@ -0,0 +1,16 @@
---
"@pnpm/plugin-commands-config": major
"pnpm": minor
---

pnpm gets its own implementation of the following commands:

* `pnpm config get`
* `pnpm config set`
* `pnpm config delete`
* `pnpm config list`

In previous versions these commands were passing through to npm CLI.

PR: [#5829](https://github.com/pnpm/pnpm/pull/5829)
Related issue: [#5621](https://github.com/pnpm/pnpm/issues/5621)
6 changes: 6 additions & 0 deletions .changeset/tiny-chicken-train.md
@@ -0,0 +1,6 @@
---
"@pnpm/config": minor
"pnpm": minor
---

pnpm reads settings from its own global configuration file at `$XDG_CONFIG_HOME/pnpm/rc` [#5829](https://github.com/pnpm/pnpm/pull/5829).
5 changes: 0 additions & 5 deletions __typings__/typed.d.ts
Expand Up @@ -20,11 +20,6 @@ declare module 'path-name' {
export = pathname;
}

declare module 'read-ini-file' {
function readIniFile (filename: string): Promise<Object>;
export = readIniFile;
}

declare module 'right-pad' {
function rightPad (txt: string, size: number): string;
export = rightPad;
Expand Down
2 changes: 1 addition & 1 deletion config/config/package.json
Expand Up @@ -50,7 +50,7 @@
"path-absolute": "^1.0.1",
"path-name": "^1.0.0",
"ramda": "npm:@pnpm/ramda@0.28.1",
"read-ini-file": "^3.1.0",
"read-ini-file": "4.0.0",
"realpath-missing": "^1.1.0",
"which": "^3.0.0"
},
Expand Down
14 changes: 10 additions & 4 deletions config/config/src/index.ts
Expand Up @@ -235,7 +235,15 @@ export async function getConfig (
'registry-supports-time-field': false,
})

npmConfig.addFile(path.resolve(path.join(__dirname, 'pnpmrc')), 'pnpm-builtin')
const configDir = getConfigDir(process)
{
const warn = npmConfig.addFile(path.join(configDir as string, 'rc'), 'pnpm-global')
if (warn) warnings.push(warn)
}
{
const warn = npmConfig.addFile(path.resolve(path.join(__dirname, 'pnpmrc')), 'pnpm-builtin')
if (warn) warnings.push(warn)
}

delete cliOptions.prefix

Expand All @@ -252,6 +260,7 @@ export async function getConfig (
pnpmConfig.maxSockets = npmConfig.maxsockets
delete pnpmConfig['maxsockets']

pnpmConfig.configDir = configDir
pnpmConfig.workspaceDir = opts.workspaceDir
pnpmConfig.workspaceRoot = cliOptions['workspace-root'] as boolean // This is needed to prevent pnpm reading workspaceRoot from env variables
pnpmConfig.rawLocalConfig = Object.assign.apply(Object, [
Expand Down Expand Up @@ -428,9 +437,6 @@ export async function getConfig (
if (!pnpmConfig.stateDir) {
pnpmConfig.stateDir = getStateDir(process)
}
if (!pnpmConfig.configDir) {
pnpmConfig.configDir = getConfigDir(process)
}
if (pnpmConfig['hoist'] === false) {
delete pnpmConfig.hoistPattern
}
Expand Down
2 changes: 1 addition & 1 deletion config/config/src/readLocalConfig.ts
@@ -1,7 +1,7 @@
import path from 'path'
import camelcaseKeys from 'camelcase-keys'
import { envReplace } from '@pnpm/config.env-replace'
import readIniFile from 'read-ini-file'
import { readIniFile } from 'read-ini-file'

export async function readLocalConfig (prefix: string) {
try {
Expand Down
15 changes: 15 additions & 0 deletions config/plugin-commands-config/README.md
@@ -0,0 +1,15 @@
# @pnpm/plugin-commands-config

> Commands for reading and writing settings to/from config files
[![npm version](https://img.shields.io/npm/v/@pnpm/plugin-commands-config.svg)](https://www.npmjs.com/package/@pnpm/plugin-commands-config)

## Installation

```sh
pnpm add @pnpm/plugin-commands-config
```

## License

MIT
3 changes: 3 additions & 0 deletions config/plugin-commands-config/jest.config.js
@@ -0,0 +1,3 @@
const config = require('../../jest.config.js')

module.exports = config
52 changes: 52 additions & 0 deletions config/plugin-commands-config/package.json
@@ -0,0 +1,52 @@
{
"name": "@pnpm/plugin-commands-config",
"version": "0.0.0",
"description": "Commands for reading and writing settings to/from config files",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"files": [
"lib",
"!*.map"
],
"engines": {
"node": ">=14.6"
},
"scripts": {
"lint": "eslint src/**/*.ts test/**/*.ts",
"_test": "jest",
"test": "pnpm run compile && pnpm run _test",
"prepublishOnly": "pnpm run compile",
"compile": "tsc --build && pnpm run lint --fix"
},
"repository": "https://github.com/pnpm/pnpm/blob/main/config/plugin-commands-config",
"keywords": [
"pnpm7",
"pnpm",
"config"
],
"license": "MIT",
"bugs": {
"url": "https://github.com/pnpm/pnpm/issues"
},
"homepage": "https://github.com/pnpm/pnpm/blob/main/config/plugin-commands-config#readme",
"dependencies": {
"@pnpm/cli-utils": "workspace:*",
"@pnpm/config": "workspace:*",
"@pnpm/error": "workspace:*",
"ini": "3.0.1",
"read-ini-file": "4.0.0",
"render-help": "^1.0.2",
"sort-keys": "^4.2.0",
"write-ini-file": "4.0.1"
},
"funding": "https://opencollective.com/pnpm",
"devDependencies": {
"@pnpm/logger": "^5.0.0",
"@pnpm/plugin-commands-config": "workspace:*",
"@pnpm/prepare": "workspace:*",
"@types/ini": "1.3.31"
},
"exports": {
".": "./lib/index.js"
}
}
8 changes: 8 additions & 0 deletions config/plugin-commands-config/src/ConfigCommandOptions.ts
@@ -0,0 +1,8 @@
import { Config } from '@pnpm/config'

export type ConfigCommandOptions = Pick<Config,
| 'configDir'
| 'dir'
| 'global'
| 'rawConfig'
> & { json?: boolean }
91 changes: 91 additions & 0 deletions config/plugin-commands-config/src/config.ts
@@ -0,0 +1,91 @@
import { docsUrl } from '@pnpm/cli-utils'
import { PnpmError } from '@pnpm/error'
import renderHelp from 'render-help'
import { configGet } from './configGet'
import { configSet } from './configSet'
import { configList } from './configList'
import { ConfigCommandOptions } from './ConfigCommandOptions'

export function rcOptionsTypes () {
return {}
}

export function cliOptionsTypes () {
return {
global: Boolean,
json: Boolean,
}
}

export const commandNames = ['config', 'c']

export function help () {
return renderHelp({
description: 'Manage the pnpm configuration files.',
descriptionLists: [
{
title: 'Commands',
list: [
{
description: 'Set the config key to the value provided',
name: 'set',
},
{
description: 'Print the config value for the provided key',
name: 'get',
},
{
description: 'Remove the config key from the config file',
name: 'delete',
},
{
description: 'Show all the config settings',
name: 'list',
},
],
},
{
title: 'Options',
list: [
{
description: 'Sets the configuration in the global config file',
name: '--global',
shortAlias: '-g',
},
],
},
],
url: docsUrl('config'),
usages: [
'pnpm config set <key> <value>',
'pnpm config get <key>',
'pnpm config delete <key>',
'pnpm config list',
],
})
}

export async function handler (opts: ConfigCommandOptions, params: string[]) {
if (params.length === 0) {
throw new PnpmError('CONFIG_NO_SUBCOMMAND', 'Please specify the subcommand', {
hint: help(),
})
}
switch (params[0]) {
case 'set': {
return configSet(opts, params[1], params[2] ?? '')
}
case 'get': {
return configGet(opts, params[1])
}
case 'delete': {
return configSet(opts, params[1], null)
}
case 'list': {
return configList(opts)
}
default: {
throw new PnpmError('CONFIG_UNKNOWN_SUBCOMMAND', 'This subcommand is not known')
}
}
}
5 changes: 5 additions & 0 deletions config/plugin-commands-config/src/configGet.ts
@@ -0,0 +1,5 @@
import { ConfigCommandOptions } from './ConfigCommandOptions'

export function configGet (opts: ConfigCommandOptions, key: string) {
return opts.rawConfig[key]
}
11 changes: 11 additions & 0 deletions config/plugin-commands-config/src/configList.ts
@@ -0,0 +1,11 @@
import { encode } from 'ini'
import sortKeys from 'sort-keys'
import { ConfigCommandOptions } from './ConfigCommandOptions'

export async function configList (opts: ConfigCommandOptions) {
const sortedConfig = sortKeys(opts.rawConfig)
if (opts.json) {
return JSON.stringify(sortedConfig, null, 2)
}
return encode(sortedConfig)
}
25 changes: 25 additions & 0 deletions config/plugin-commands-config/src/configSet.ts
@@ -0,0 +1,25 @@
import path from 'path'
import { readIniFile } from 'read-ini-file'
import { writeIniFile } from 'write-ini-file'
import { ConfigCommandOptions } from './ConfigCommandOptions'

export async function configSet (opts: ConfigCommandOptions, key: string, value: string | null) {
const configPath = opts.global ? path.join(opts.configDir, 'rc') : path.join(opts.dir, '.npmrc')
const settings = await safeReadIniFile(configPath)
if (value == null) {
if (settings[key] == null) return
delete settings[key]
} else {
settings[key] = value
}
await writeIniFile(configPath, settings)
}

async function safeReadIniFile (configPath: string) {
try {
return await readIniFile(configPath)
} catch (err: any) { // eslint-disable-line @typescript-eslint/no-explicit-any
if (err.code === 'ENOENT') return {}
throw err
}
}
12 changes: 12 additions & 0 deletions config/plugin-commands-config/src/get.ts
@@ -0,0 +1,12 @@
import * as configCmd from './config'
import { ConfigCommandOptions } from './ConfigCommandOptions'

export const rcOptionsTypes = configCmd.rcOptionsTypes
export const cliOptionsTypes = configCmd.cliOptionsTypes
export const help = configCmd.help

export const commandNames = ['get']

export async function handler (opts: ConfigCommandOptions, params: string[]) {
return configCmd.handler(opts, ['get', ...params])
}
5 changes: 5 additions & 0 deletions config/plugin-commands-config/src/index.ts
@@ -0,0 +1,5 @@
import * as config from './config'
import * as getCommand from './get'
import * as setCommand from './set'

export { config, getCommand, setCommand }
12 changes: 12 additions & 0 deletions config/plugin-commands-config/src/set.ts
@@ -0,0 +1,12 @@
import * as configCmd from './config'
import { ConfigCommandOptions } from './ConfigCommandOptions'

export const rcOptionsTypes = configCmd.rcOptionsTypes
export const cliOptionsTypes = configCmd.cliOptionsTypes
export const help = configCmd.help

export const commandNames = ['set']

export async function handler (opts: ConfigCommandOptions, params: string[]) {
return configCmd.handler(opts, ['set', ...params])
}
24 changes: 24 additions & 0 deletions config/plugin-commands-config/test/configDelete.test.ts
@@ -0,0 +1,24 @@
import fs from 'fs'
import path from 'path'
import { tempDir } from '@pnpm/prepare'
import { config } from '@pnpm/plugin-commands-config'
import { readIniFileSync } from 'read-ini-file'

test('config delete', async () => {
const tmp = tempDir()
const configDir = path.join(tmp, 'global-config')
fs.mkdirSync(configDir, { recursive: true })
fs.writeFileSync(path.join(configDir, 'rc'), `store-dir=~/store
cache-dir=~/cache`)

await config.handler({
dir: process.cwd(),
configDir,
global: true,
rawConfig: {},
}, ['delete', 'store-dir'])

expect(readIniFileSync(path.join(configDir, 'rc'))).toEqual({
'cache-dir': '~/cache',
})
})
14 changes: 14 additions & 0 deletions config/plugin-commands-config/test/configGet.test.ts
@@ -0,0 +1,14 @@
import { config } from '@pnpm/plugin-commands-config'

test('config get', async () => {
const configKey = await config.handler({
dir: process.cwd(),
configDir: process.cwd(),
global: true,
rawConfig: {
'store-dir': '~/store',
},
}, ['get', 'store-dir'])

expect(configKey).toEqual('~/store')
})

0 comments on commit 841f52e

Please sign in to comment.