Skip to content

Commit

Permalink
feat: introduce the npm config fix command
Browse files Browse the repository at this point in the history
  • Loading branch information
nlf authored and fritzy committed Oct 13, 2022
1 parent d2963c6 commit a09e19d
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 2 deletions .
50 changes: 49 additions & 1 deletion lib/commands/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class Config extends BaseCommand {
'delete <key> [<key> ...]' ,
'list [--json]' ,
'edit' ,
'fix' ,
]

static params = [
Expand All @@ -72,7 +73,7 @@ class Config extends BaseCommand {
}

if ( argv . length === two ) {
const cmds = [ 'get' , 'set' , 'delete' , 'ls' , 'rm' , 'edit' ]
const cmds = [ 'get' , 'set' , 'delete' , 'ls' , 'rm' , 'edit' , 'fix' ]
if ( opts . partialWord !== 'l' ) {
cmds . push ( 'list' )
}
Expand All @@ -97,6 +98,7 @@ class Config extends BaseCommand {
case 'edit' :
case 'list' :
case 'ls' :
case 'fix' :
default :
return [ ]
}
Expand Down Expand Up @@ -129,6 +131,9 @@ class Config extends BaseCommand {
case 'edit' :
await this . edit ( )
break
case 'fix' :
await this . fix ( )
break
default :
throw this . usageError ( )
}
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions tap-snapshots/test/lib/docs.js.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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>]
Expand All @@ -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
\`\`\`
Expand Down
99 changes: 98 additions & 1 deletion test/lib/commands/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 )

Expand All @@ -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' ] , [ ] )

Expand Down

0 comments on commit a09e19d

Please sign in to comment.