1
1
const t = require ( 'tap' )
2
- const spawn = require ( '@npmcli/promise-spawn' )
3
2
const { spawnSync } = require ( 'child_process' )
4
- const { resolve, join } = require ( 'path' )
5
- const { readFileSync, chmodSync } = require ( 'fs' )
3
+ const { resolve, join, extname , sep } = require ( 'path' )
4
+ const { readFileSync, chmodSync, readdirSync } = require ( 'fs' )
6
5
const Diff = require ( 'diff' )
6
+ const { sync : which } = require ( 'which' )
7
7
const { version } = require ( '../../package.json' )
8
8
9
- const root = resolve ( __dirname , '../..' )
10
- const npmShim = join ( root , 'bin/npm' )
11
- const npxShim = join ( root , 'bin/npx' )
9
+ const ROOT = resolve ( __dirname , '../..' )
10
+ const BIN = join ( ROOT , 'bin' )
11
+ const SHIMS = readdirSync ( BIN ) . reduce ( ( acc , shim ) => {
12
+ if ( extname ( shim ) !== '.js' ) {
13
+ acc [ shim ] = readFileSync ( join ( BIN , shim ) , 'utf-8' )
14
+ }
15
+ return acc
16
+ } , { } )
17
+
18
+ // windows requires each segment of a command path to be quoted when using shell: true
19
+ const quoteWhich = ( cmd ) => which ( cmd )
20
+ . split ( sep )
21
+ . map ( p => p . includes ( ' ' ) ? `"${ p } "` : p )
22
+ . join ( sep )
12
23
13
- t . test ( 'npm vs npx ' , t => {
24
+ t . test ( 'shim contents ' , t => {
14
25
// these scripts should be kept in sync so this tests the contents of each
15
26
// and does a diff to ensure the only differences between them are necessary
16
- const diffFiles = ( ext = '' ) => Diff . diffChars (
17
- readFileSync ( `${ npmShim } ${ ext } ` , 'utf8' ) ,
18
- readFileSync ( `${ npxShim } ${ ext } ` , 'utf8' )
19
- ) . filter ( v => v . added || v . removed ) . map ( ( v , i ) => i === 0 ? v . value : v . value . toUpperCase ( ) )
27
+ const diffFiles = ( npm , npx ) => Diff . diffChars ( npm , npx )
28
+ . filter ( v => v . added || v . removed )
29
+ . reduce ( ( acc , v ) => {
30
+ if ( v . value . length === 1 ) {
31
+ acc . letters . add ( v . value . toUpperCase ( ) )
32
+ } else {
33
+ acc . diff . push ( v . value )
34
+ }
35
+ return acc
36
+ } , { diff : [ ] , letters : new Set ( ) } )
37
+
38
+ t . plan ( 3 )
20
39
21
40
t . test ( 'bash' , t => {
22
- const [ npxCli , ...changes ] = diffFiles ( )
23
- const npxCliLine = npxCli . split ( '\n' ) . reverse ( ) . join ( '' )
24
- t . match ( npxCliLine , / ^ N P X _ C L I _ J S = / , 'has NPX_CLI' )
25
- t . equal ( changes . length , 20 )
26
- t . strictSame ( [ ...new Set ( changes ) ] , [ 'M' , 'X' ] , 'all other changes are m->x' )
41
+ const { diff, letters } = diffFiles ( SHIMS . npm , SHIMS . npx )
42
+ t . match ( diff [ 0 ] . split ( '\n' ) . reverse ( ) . join ( '' ) , / ^ N P X _ C L I _ J S = / , 'has NPX_CLI' )
43
+ t . equal ( diff . length , 1 )
44
+ t . strictSame ( [ ...letters ] , [ 'M' , 'X' ] , 'all other changes are m->x' )
27
45
t . end ( )
28
46
} )
29
47
30
48
t . test ( 'cmd' , t => {
31
- const [ npxCli , ... changes ] = diffFiles ( ' .cmd')
32
- t . match ( npxCli , / ^ S E T " N P X _ C L I _ J S = / , 'has NPX_CLI' )
33
- t . equal ( changes . length , 12 )
34
- t . strictSame ( [ ...new Set ( changes ) ] , [ 'M' , 'X' ] , 'all other changes are m->x' )
49
+ const { diff , letters } = diffFiles ( SHIMS [ 'npm .cmd'] , SHIMS [ 'npx.cmd' ] )
50
+ t . match ( diff [ 0 ] , / ^ S E T " N P X _ C L I _ J S = / , 'has NPX_CLI' )
51
+ t . equal ( diff . length , 1 )
52
+ t . strictSame ( [ ...letters ] , [ 'M' , 'X' ] , 'all other changes are m->x' )
35
53
t . end ( )
36
54
} )
37
55
38
- t . end ( )
56
+ t . test ( 'pwsh' , t => {
57
+ const { diff, letters } = diffFiles ( SHIMS [ 'npm.ps1' ] , SHIMS [ 'npx.ps1' ] )
58
+ t . equal ( diff . length , 0 )
59
+ t . strictSame ( [ ...letters ] , [ 'M' , 'X' ] , 'all other changes are m->x' )
60
+ t . end ( )
61
+ } )
39
62
} )
40
63
41
- t . test ( 'basic' , async t => {
42
- if ( process . platform !== 'win32' ) {
43
- t . comment ( 'test only relevant on windows' )
44
- return
45
- }
46
-
64
+ t . test ( 'run shims' , t => {
47
65
const path = t . testdir ( {
66
+ ...SHIMS ,
48
67
'node.exe' : readFileSync ( process . execPath ) ,
49
- npm : readFileSync ( npmShim ) ,
50
- npx : readFileSync ( npxShim ) ,
51
68
// simulate the state where one version of npm is installed
52
69
// with node, but we should load the globally installed one
53
70
'global-prefix' : {
54
71
node_modules : {
55
- npm : t . fixture ( 'symlink' , root ) ,
72
+ npm : t . fixture ( 'symlink' , ROOT ) ,
56
73
} ,
57
74
} ,
58
75
// put in a shim that ONLY prints the intended global prefix,
59
76
// and should not be used for anything else.
60
77
node_modules : {
61
78
npm : {
62
79
bin : {
63
- 'npx-cli.js' : `
64
- throw new Error('this should not be called')
65
- ` ,
80
+ 'npx-cli.js' : `throw new Error('this should not be called')` ,
66
81
'npm-cli.js' : `
67
82
const assert = require('assert')
68
83
const args = process.argv.slice(2)
@@ -76,70 +91,96 @@ t.test('basic', async t => {
76
91
} ,
77
92
} )
78
93
79
- chmodSync ( join ( path , 'npm' ) , 0o755 )
80
- chmodSync ( join ( path , 'npx' ) , 0o755 )
81
-
82
- const { ProgramFiles, SystemRoot, NYC_CONFIG } = process . env
83
- const gitBash = join ( ProgramFiles , 'Git' , 'bin' , 'bash.exe' )
84
- const gitUsrBinBash = join ( ProgramFiles , 'Git' , 'usr' , 'bin' , 'bash.exe' )
85
- const wslBash = join ( SystemRoot , 'System32' , 'bash.exe' )
86
- const cygwinBash = join ( SystemRoot , '/' , 'cygwin64' , 'bin' , 'bash.exe' )
87
-
88
- const bashes = Object . entries ( {
89
- 'wsl bash' : wslBash ,
90
- 'git bash' : gitBash ,
91
- 'git internal bash' : gitUsrBinBash ,
92
- 'cygwin bash' : cygwinBash ,
93
- } ) . map ( ( [ name , bash ] ) => {
94
- let skip
95
- if ( bash === cygwinBash && NYC_CONFIG ) {
96
- skip = 'does not play nicely with NYC, run without coverage'
97
- } else {
94
+ const spawn = ( cmd , args , opts ) => {
95
+ const result = spawnSync ( cmd , args , {
96
+ // don't hit the registry for the update check
97
+ env : { PATH : path , npm_config_update_notifier : 'false' } ,
98
+ cwd : path ,
99
+ windowsHide : true ,
100
+ ...opts ,
101
+ } )
102
+ result . stdout = result . stdout . toString ( ) . trim ( )
103
+ result . stderr = result . stderr . toString ( ) . trim ( )
104
+ return result
105
+ }
106
+
107
+ for ( const shim of Object . keys ( SHIMS ) ) {
108
+ chmodSync ( join ( path , shim ) , 0o755 )
109
+ }
110
+
111
+ const { ProgramFiles = '' , SystemRoot = '' , NYC_CONFIG , WINDOWS_SHIMS_TEST } = process . env
112
+ const failOnMissing = WINDOWS_SHIMS_TEST === 'fail'
113
+ const defaultSkip = process . platform === 'win32' ? null : 'test on relevant on windows'
114
+
115
+ const matchSpawn = ( t , cmd , bin = '' , { skip = defaultSkip , name } = { } ) => {
116
+ const testName = `${ name || cmd } ${ bin } ` . trim ( )
117
+ if ( skip ) {
118
+ if ( failOnMissing ) {
119
+ t . fail ( testName )
120
+ } else {
121
+ t . skip ( `${ testName } - ${ skip } ` )
122
+ }
123
+ return
124
+ }
125
+ t . test ( testName , t => {
126
+ t . plan ( 1 )
127
+ const isNpm = testName . includes ( 'npm' )
128
+ const binArg = isNpm ? 'help' : '--version'
129
+ const args = [ ]
130
+ const opts = { }
131
+ if ( cmd . endsWith ( '.cmd' ) ) {
132
+ args . push ( binArg )
133
+ } else if ( cmd === 'pwsh' ) {
134
+ cmd = quoteWhich ( cmd )
135
+ args . push ( `${ bin } .ps1` , binArg )
136
+ opts . shell = true
137
+ } else if ( cmd . endsWith ( 'bash.exe' ) ) {
138
+ // only cygwin *requires* the -l, but the others are ok with it
139
+ args . push ( '-l' , bin , binArg )
140
+ }
141
+ t . match ( spawn ( cmd , args , opts ) , {
142
+ status : 0 ,
143
+ signal : null ,
144
+ stderr : '' ,
145
+ stdout : isNpm ? `npm@${ version } ${ ROOT } ` : version ,
146
+ } , 'command output is correct' )
147
+ } )
148
+ }
149
+
150
+ // ensure that all tests are either run or skipped
151
+ t . plan ( 12 )
152
+
153
+ matchSpawn ( t , 'npm.cmd' )
154
+ matchSpawn ( t , 'npx.cmd' )
155
+ matchSpawn ( t , 'pwsh' , 'npm' )
156
+ matchSpawn ( t , 'pwsh' , 'npx' )
157
+
158
+ const bashes = [
159
+ { name : 'git' , cmd : join ( ProgramFiles , 'Git' , 'bin' , 'bash.exe' ) } ,
160
+ { name : 'user git' , cmd : join ( ProgramFiles , 'Git' , 'usr' , 'bin' , 'bash.exe' ) } ,
161
+ { name : 'wsl' , cmd : join ( SystemRoot , 'System32' , 'bash.exe' ) } ,
162
+ {
163
+ name : 'cygwin' ,
164
+ cmd : join ( SystemRoot , '/' , 'cygwin64' , 'bin' , 'bash.exe' ) ,
165
+ skip : NYC_CONFIG ? 'does not play nicely with nyc' : undefined ,
166
+ } ,
167
+ ] . map ( ( { name, cmd, skip = defaultSkip } ) => {
168
+ if ( ! skip ) {
98
169
try {
99
170
// If WSL is installed, it *has* a bash.exe, but it fails if
100
171
// there is no distro installed, so we need to detect that.
101
- if ( spawnSync ( bash , [ '-l' , '-c' , 'exit 0' ] ) . status !== 0 ) {
172
+ if ( spawnSync ( cmd , [ '-l' , '-c' , 'exit 0' ] ) . status !== 0 ) {
102
173
throw new Error ( 'not installed' )
103
174
}
104
- } catch {
105
- skip = 'not installed'
175
+ } catch ( err ) {
176
+ skip = err . message
106
177
}
107
178
}
108
- return { name , bash , skip }
179
+ return { cmd , skip , name : ` ${ name } bash` }
109
180
} )
110
181
111
- for ( const { name, bash, skip } of bashes ) {
112
- if ( skip ) {
113
- t . skip ( name , { diagnostic : true , bin : bash , reason : skip } )
114
- continue
115
- }
116
-
117
- await t . test ( name , async t => {
118
- const bins = Object . entries ( {
119
- // should have loaded this instance of npm we symlinked in
120
- npm : [ [ 'help' ] , `npm@${ version } ${ root } ` ] ,
121
- npx : [ [ '--version' ] , version ] ,
122
- } )
123
-
124
- for ( const [ binName , [ cmdArgs , stdout ] ] of bins ) {
125
- await t . test ( binName , async t => {
126
- // only cygwin *requires* the -l, but the others are ok with it
127
- const args = [ '-l' , binName , ...cmdArgs ]
128
- const result = await spawn ( bash , args , {
129
- // don't hit the registry for the update check
130
- env : { PATH : path , npm_config_update_notifier : 'false' } ,
131
- cwd : path ,
132
- } )
133
- t . match ( result , {
134
- cmd : bash ,
135
- args : args ,
136
- code : 0 ,
137
- signal : null ,
138
- stderr : String ,
139
- stdout,
140
- } )
141
- } )
142
- }
143
- } )
182
+ for ( const { cmd, skip, name } of bashes ) {
183
+ matchSpawn ( t , cmd , 'npm' , { name, skip } )
184
+ matchSpawn ( t , cmd , 'npx' , { name, skip } )
144
185
}
145
186
} )
0 commit comments