@@ -146,7 +146,7 @@ t.test('flags() throws error for unknown flags', async t => {
146146 command . exec ( ) ,
147147 {
148148 code : 'EUNKNOWNCONFIG' ,
149- message : / U n k n o w n c l i c o n f i g : [ \s \S ] * - - u n k n o w n - f l a g / ,
149+ message : / U n k n o w n c l i f l a g : [ \s \S ] * - - u n k n o w n - f l a g / ,
150150 } ,
151151 'throws EUNKNOWNCONFIG for unknown flag'
152152 )
@@ -448,9 +448,9 @@ t.test('flags() throws error for multiple unknown flags with pluralization', asy
448448 command . exec ( ) ,
449449 {
450450 code : 'EUNKNOWNCONFIG' ,
451- message : / U n k n o w n c l i c o n f i g s : [ \s \S ] * - - u n k n o w n - o n e [ \s \S ] * - - u n k n o w n - t w o / ,
451+ message : / U n k n o w n c l i f l a g s : [ \s \S ] * - - u n k n o w n - o n e [ \s \S ] * - - u n k n o w n - t w o / ,
452452 } ,
453- 'throws EUNKNOWNCONFIG with pluralized "configs " for multiple unknown flags'
453+ 'throws EUNKNOWNCONFIG with pluralized "flags " for multiple unknown flags'
454454 )
455455} )
456456
@@ -685,14 +685,59 @@ t.test('flags() does not throw when positionals is null (unlimited)', async t =>
685685 t . equal ( flags . id , null , 'id flag uses default' )
686686} )
687687
688- t . test ( 'validateCli throws aggregated error for unknown file configs' , async t => {
688+ t . test ( 'validateCli warns (does not throw) for unknown file configs' , async t => {
689+ const { npm, logs } = await loadMockNpm ( t , {
690+ homeDir : {
691+ '.npmrc' : [
692+ 'bogus-user-key=yes' ,
693+ '@scope:bogus-scoped=no' ,
694+ ] . join ( '\n' ) ,
695+ } ,
696+ } )
697+
698+ class TestCommand extends BaseCommand {
699+ static name = 'test-command'
700+ static description = 'Test command'
701+ }
702+
703+ const command = new TestCommand ( npm )
704+ t . doesNotThrow ( ( ) => command . validateCli ( ) , 'unknown file configs do not throw' )
705+
706+ const warningLogs = logs . warn
707+ t . ok ( warningLogs . some ( msg => msg . includes ( 'bogus-user-key' ) && msg . includes ( 'npm help npmrc' ) ) ,
708+ 'warns about plain unknown key' )
709+ t . ok ( warningLogs . some ( msg => msg . includes ( 'bogus-scoped' ) ) ,
710+ 'warns about scoped unknown key' )
711+ } )
712+
713+ t . test ( 'validateCli unknown file config warning respects loglevel suppression' , async t => {
714+ const { npm, logs } = await loadMockNpm ( t , {
715+ homeDir : {
716+ '.npmrc' : 'bogus-user-key=yes' ,
717+ } ,
718+ config : { loglevel : 'error' } ,
719+ } )
720+
721+ class TestCommand extends BaseCommand {
722+ static name = 'test-command'
723+ static description = 'Test command'
724+ }
725+
726+ const command = new TestCommand ( npm )
727+ t . doesNotThrow ( ( ) => command . validateCli ( ) , 'still does not throw' )
728+ t . notOk ( logs . warn . some ( msg => msg . includes ( 'bogus-user-key' ) ) ,
729+ 'unknown file config warning is suppressed at loglevel=error' )
730+ } )
731+
732+ t . test ( 'validateCli errors on unknown file configs when strict-npmrc is set' , async t => {
689733 const { npm } = await loadMockNpm ( t , {
690734 homeDir : {
691735 '.npmrc' : [
692736 'bogus-user-key=yes' ,
693737 '@scope:bogus-scoped=no' ,
694738 ] . join ( '\n' ) ,
695739 } ,
740+ config : { 'strict-npmrc' : true } ,
696741 } )
697742
698743 class TestCommand extends BaseCommand {
@@ -707,31 +752,46 @@ t.test('validateCli throws aggregated error for unknown file configs', async t =
707752 code : 'EUNKNOWNCONFIG' ,
708753 message : / U n k n o w n n p m c o n f i g u r a t i o n k e y s : [ \s \S ] * b o g u s - u s e r - k e y [ \s \S ] * b o g u s - s c o p e d [ \s \S ] * n p m h e l p n p m r c / ,
709754 } ,
710- 'throws EUNKNOWNCONFIG aggregating plain and scoped keys'
755+ 'throws EUNKNOWNCONFIG aggregating plain and scoped keys under strict-npmrc '
711756 )
712757} )
713758
714- t . test ( 'validateCli uses singular "key" when only one unknown file config ' , async t => {
759+ t . test ( 'validateCli combines cli and file unknowns under strict-npmrc ' , async t => {
715760 const { npm } = await loadMockNpm ( t , {
716761 homeDir : {
717- '.npmrc' : 'only-one-bad-key=yes' ,
762+ '.npmrc' : [
763+ 'strict-npmrc=true' ,
764+ 'bogus-user-key=yes' ,
765+ ] . join ( '\n' ) ,
718766 } ,
767+ npm : { argv : [ 'test-command' , '--unknown-cli' ] } ,
719768 } )
720769
721770 class TestCommand extends BaseCommand {
722771 static name = 'test-command'
723772 static description = 'Test command'
773+ async exec ( ) {
774+ return this . flags ( )
775+ }
724776 }
725777
726778 const command = new TestCommand ( npm )
727- await t . rejects (
728- ( async ( ) => command . validateCli ( ) ) ( ) ,
729- {
730- code : 'EUNKNOWNCONFIG' ,
731- message : / U n k n o w n n p m c o n f i g u r a t i o n k e y : \n / ,
732- } ,
733- 'singular phrasing when only one unknown key'
734- )
779+ let err
780+ try {
781+ command . validateCli ( )
782+ t . fail ( 'expected throw' )
783+ } catch ( e ) {
784+ err = e
785+ }
786+ t . equal ( err . code , 'EUNKNOWNCONFIG' )
787+ t . match ( err . message , / U n k n o w n c l i f l a g : [ \s \S ] * - - u n k n o w n - c l i / ,
788+ 'message contains the cli section' )
789+ t . match ( err . message , / U n k n o w n n p m c o n f i g u r a t i o n k e y : [ \s \S ] * b o g u s - u s e r - k e y / ,
790+ 'message contains the singular file section' )
791+ t . match ( err . unknownConfigs , [
792+ { where : 'cli' , key : 'unknown-cli' } ,
793+ { where : 'user' , key : 'bogus-user-key' } ,
794+ ] , 'structured payload contains both cli and file entries' )
735795} )
736796
737797t . test ( 'validateCli bypasses checks when skipConfigValidation is set' , async t => {
@@ -751,8 +811,8 @@ t.test('validateCli bypasses checks when skipConfigValidation is set', async t =
751811 t . doesNotThrow ( ( ) => command . validateCli ( ) , 'skipConfigValidation bypasses unknown-file-config check' )
752812} )
753813
754- t . test ( 'validateCli combines cli and file unknowns into a single error ' , async t => {
755- const { npm } = await loadMockNpm ( t , {
814+ t . test ( 'validateCli throws only on cli unknowns while warning on file unknowns ' , async t => {
815+ const { npm, logs } = await loadMockNpm ( t , {
756816 homeDir : {
757817 '.npmrc' : 'bogus-user-key=yes' ,
758818 } ,
@@ -776,14 +836,15 @@ t.test('validateCli combines cli and file unknowns into a single error', async t
776836 err = e
777837 }
778838 t . equal ( err . code , 'EUNKNOWNCONFIG' )
779- t . match ( err . message , / U n k n o w n c l i c o n f i g : [ \s \S ] * - - u n k n o w n - c l i / ,
839+ t . match ( err . message , / U n k n o w n c l i f l a g : [ \s \S ] * - - u n k n o w n - c l i / ,
780840 'message contains the cli section' )
781- t . match ( err . message , / U n k n o w n n p m c o n f i g u r a t i o n k e y : [ \s \S ] * b o g u s - u s e r - k e y / ,
782- 'message contains the file section ' )
841+ t . notMatch ( err . message , / b o g u s - u s e r - k e y / ,
842+ 'error message does not contain the file config ' )
783843 t . match ( err . unknownConfigs , [
784844 { where : 'cli' , key : 'unknown-cli' } ,
785- { where : 'user' , key : 'bogus-user-key' } ,
786- ] , 'structured payload contains both cli and file entries' )
845+ ] , 'structured payload contains only cli entries' )
846+ t . ok ( logs . warn . some ( msg => msg . includes ( 'bogus-user-key' ) ) ,
847+ 'file unknown is warned, not thrown' )
787848} )
788849
789850t . test ( 'validateCli does not throw on unknown env (npm_config_*) configs' , async t => {
@@ -830,7 +891,7 @@ t.test('flags() throws with scoped (nerfdart) display for unknown cli config', a
830891 command . exec ( ) ,
831892 {
832893 code : 'EUNKNOWNCONFIG' ,
833- message : / U n k n o w n c l i c o n f i g : [ \s \S ] * - - b o g u s \( @ s c o p e : b o g u s \) / ,
894+ message : / U n k n o w n c l i f l a g : [ \s \S ] * - - b o g u s \( @ s c o p e : b o g u s \) / ,
834895 } ,
835896 'scoped display form uses baseKey (key) layout, throws EUNKNOWNCONFIG'
836897 )
0 commit comments