Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/npm/cli.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authornlf <quitlahok@gmail.com>2022-10-06 22:50:36 +0300
committerNathan Fritz <fritzy@github.com>2022-10-13 18:46:17 +0300
commita09e19d88f046e54e8d75343883635a1bd056310 (patch)
treeb32853b4422b27034f2afd1554f3a5935a4592dd
parentd2963c67b992b9b3b9dd32f6f41cbbe4bcc580c8 (diff)
feat: introduce the `npm config fix` command
-rw-r--r--lib/commands/config.js50
-rw-r--r--tap-snapshots/test/lib/docs.js.test.cjs2
-rw-r--r--test/lib/commands/config.js99
3 files changed, 149 insertions, 2 deletions
diff --git a/lib/commands/config.js b/lib/commands/config.js
index 8d74b8d27..e6c0ba79d 100644
--- a/lib/commands/config.js
+++ b/lib/commands/config.js
@@ -51,6 +51,7 @@ class Config extends BaseCommand {
'delete <key> [<key> ...]',
'list [--json]',
'edit',
+ 'fix',
]
static params = [
@@ -72,7 +73,7 @@ class Config extends BaseCommand {
}
if (argv.length === 2) {
- const cmds = ['get', 'set', 'delete', 'ls', 'rm', 'edit']
+ const cmds = ['get', 'set', 'delete', 'ls', 'rm', 'edit', 'fix']
if (opts.partialWord !== 'l') {
cmds.push('list')
}
@@ -97,6 +98,7 @@ class Config extends BaseCommand {
case 'edit':
case 'list':
case 'ls':
+ case 'fix':
default:
return []
}
@@ -129,6 +131,9 @@ class Config extends BaseCommand {
case 'edit':
await this.edit()
break
+ case 'fix':
+ await this.fix()
+ break
default:
throw this.usageError()
}
@@ -240,6 +245,49 @@ ${defData}
})
}
+ async fix () {
+ let problems
+
+ try {
+ this.npm.config.validate()
+ return // if validate doesn't throw we have nothing to do
+ } catch (err) {
+ // coverage skipped because we don't need to test rethrowing errors
+ // istanbul ignore next
+ if (err.code !== 'ERR_INVALID_AUTH') {
+ throw err
+ }
+
+ problems = err.problems
+ }
+
+ if (!this.npm.config.isDefault('location')) {
+ problems = problems.filter((problem) => {
+ return problem.where === this.npm.config.get('location')
+ })
+ }
+
+ this.npm.config.repair(problems)
+ const locations = []
+
+ this.npm.output('The following configuration problems have been repaired:\n')
+ const summary = problems.map(({ action, from, to, key, where }) => {
+ // coverage disabled for else branch because it is intentionally omitted
+ // istanbul ignore else
+ if (action === 'rename') {
+ // we keep track of which configs were modified here so we know what to save later
+ locations.push(where)
+ return `~ \`${from}\` renamed to \`${to}\` in ${where} config`
+ } else if (action === 'delete') {
+ locations.push(where)
+ return `- \`${key}\` deleted from ${where} config`
+ }
+ }).join('\n')
+ this.npm.output(summary)
+
+ return await Promise.all(locations.map((location) => this.npm.config.save(location)))
+ }
+
async list () {
const msg = []
// long does not have a flattener
diff --git a/tap-snapshots/test/lib/docs.js.test.cjs b/tap-snapshots/test/lib/docs.js.test.cjs
index 44072441c..81be4ec21 100644
--- a/tap-snapshots/test/lib/docs.js.test.cjs
+++ b/tap-snapshots/test/lib/docs.js.test.cjs
@@ -2650,6 +2650,7 @@ npm config get [<key> [<key> ...]]
npm config delete <key> [<key> ...]
npm config list [--json]
npm config edit
+npm config fix
Options:
[--json] [-g|--global] [--editor <editor>] [-L|--location <global|user|project>]
@@ -2665,6 +2666,7 @@ npm config get [<key> [<key> ...]]
npm config delete <key> [<key> ...]
npm config list [--json]
npm config edit
+npm config fix
alias: c
\`\`\`
diff --git a/test/lib/commands/config.js b/test/lib/commands/config.js
index 61e47244e..c94e0df13 100644
--- a/test/lib/commands/config.js
+++ b/test/lib/commands/config.js
@@ -411,6 +411,102 @@ t.test('config edit - editor exits non-0', async t => {
)
})
+t.test('config fix', (t) => {
+ t.test('no problems', async (t) => {
+ const home = t.testdir({
+ '.npmrc': '',
+ })
+
+ const sandbox = new Sandbox(t, { home })
+ await sandbox.run('config', ['fix'])
+ t.equal(sandbox.output, '', 'printed nothing')
+ })
+
+ t.test('repairs all configs by default', async (t) => {
+ const root = t.testdir({
+ global: {
+ npmrc: '_authtoken=notatoken\n_authToken=afaketoken',
+ },
+ home: {
+ '.npmrc': '_authtoken=thisisinvalid\n_auth=beef',
+ },
+ })
+ const registry = `//registry.npmjs.org/`
+
+ const sandbox = new Sandbox(t, {
+ global: join(root, 'global'),
+ home: join(root, 'home'),
+ })
+ await sandbox.run('config', ['fix'])
+
+ // global config fixes
+ t.match(sandbox.output, '`_authtoken` deleted from global config',
+ 'output has deleted global _authtoken')
+ t.match(sandbox.output, `\`_authToken\` renamed to \`${registry}:_authToken\` in global config`,
+ 'output has renamed global _authToken')
+ t.not(sandbox.config.get('_authtoken', 'global'), '_authtoken is not set globally')
+ t.not(sandbox.config.get('_authToken', 'global'), '_authToken is not set globally')
+ t.equal(sandbox.config.get(`${registry}:_authToken`, 'global'), 'afaketoken',
+ 'global _authToken was scoped')
+ const globalConfig = await readFile(join(root, 'global', 'npmrc'), { encoding: 'utf8' })
+ t.equal(globalConfig, `${registry}:_authToken=afaketoken\n`, 'global config was written')
+
+ // user config fixes
+ t.match(sandbox.output, '`_authtoken` deleted from user config',
+ 'output has deleted user _authtoken')
+ t.match(sandbox.output, `\`_auth\` renamed to \`${registry}:_auth\` in user config`,
+ 'output has renamed user _auth')
+ t.not(sandbox.config.get('_authtoken', 'user'), '_authtoken is not set in user config')
+ t.not(sandbox.config.get('_auth'), '_auth is not set in user config')
+ t.equal(sandbox.config.get(`${registry}:_auth`, 'user'), 'beef', 'user _auth was scoped')
+ const userConfig = await readFile(join(root, 'home', '.npmrc'), { encoding: 'utf8' })
+ t.equal(userConfig, `${registry}:_auth=beef\n`, 'user config was written')
+ })
+
+ t.test('repairs only the config specified by --location if asked', async (t) => {
+ const root = t.testdir({
+ global: {
+ npmrc: '_authtoken=notatoken\n_authToken=afaketoken',
+ },
+ home: {
+ '.npmrc': '_authtoken=thisisinvalid\n_auth=beef',
+ },
+ })
+ const registry = `//registry.npmjs.org/`
+
+ const sandbox = new Sandbox(t, {
+ global: join(root, 'global'),
+ home: join(root, 'home'),
+ })
+ await sandbox.run('config', ['fix', '--location=user'])
+
+ // global config should be untouched
+ t.notMatch(sandbox.output, '`_authtoken` deleted from global',
+ 'output has deleted global _authtoken')
+ t.notMatch(sandbox.output, `\`_authToken\` renamed to \`${registry}:_authToken\` in global`,
+ 'output has renamed global _authToken')
+ t.equal(sandbox.config.get('_authtoken', 'global'), 'notatoken', 'global _authtoken untouched')
+ t.equal(sandbox.config.get('_authToken', 'global'), 'afaketoken', 'global _authToken untouched')
+ t.not(sandbox.config.get(`${registry}:_authToken`, 'global'), 'global _authToken not scoped')
+ const globalConfig = await readFile(join(root, 'global', 'npmrc'), { encoding: 'utf8' })
+ t.equal(globalConfig, '_authtoken=notatoken\n_authToken=afaketoken',
+ 'global config was not written')
+
+ // user config fixes
+ t.match(sandbox.output, '`_authtoken` deleted from user',
+ 'output has deleted user _authtoken')
+ t.match(sandbox.output, `\`_auth\` renamed to \`${registry}:_auth\` in user`,
+ 'output has renamed user _auth')
+ t.not(sandbox.config.get('_authtoken', 'user'), '_authtoken is not set in user config')
+ t.not(sandbox.config.get('_auth', 'user'), '_auth is not set in user config')
+ t.equal(sandbox.config.get(`${registry}:_auth`, 'user'), 'beef', 'user _auth was scoped')
+ const userConfig = await readFile(join(root, 'home', '.npmrc'), { encoding: 'utf8' })
+ t.equal(userConfig, `${registry}:_auth=beef\n`, 'user config was written')
+ })
+
+ t.end()
+})
+
t.test('completion', async t => {
const sandbox = new Sandbox(t)
@@ -423,13 +519,14 @@ t.test('completion', async t => {
sandbox.reset()
}
- await testComp([], ['get', 'set', 'delete', 'ls', 'rm', 'edit', 'list'])
+ await testComp([], ['get', 'set', 'delete', 'ls', 'rm', 'edit', 'fix', 'list'])
await testComp(['set', 'foo'], [])
await testComp(['get'], allKeys)
await testComp(['set'], allKeys)
await testComp(['delete'], allKeys)
await testComp(['rm'], allKeys)
await testComp(['edit'], [])
+ await testComp(['fix'], [])
await testComp(['list'], [])
await testComp(['ls'], [])