1const t = require('tap') 2const mockNpm = require('../../fixtures/mock-npm') 3const { cleanCwd } = require('../../fixtures/clean-snapshot') 4 5const mockExplore = async (t, exec, { 6 PJ_ERROR = null, 7 RUN_SCRIPT_ERROR = null, 8 RUN_SCRIPT_EXIT_CODE = 0, 9 RUN_SCRIPT_SIGNAL = null, 10} = {}) => { 11 let PJ_CALLED = '' 12 const mockPJ = { 13 normalize: async path => { 14 if (PJ_ERROR) { 15 throw PJ_ERROR 16 } 17 PJ_CALLED = cleanCwd(path) 18 return { content: { some: 'package' } } 19 }, 20 } 21 22 let RUN_SCRIPT_EXEC = null 23 const mockRunScript = ({ pkg, event }) => { 24 if (event !== '_explore') { 25 throw new Error('got wrong event name') 26 } 27 28 RUN_SCRIPT_EXEC = pkg.scripts._explore 29 30 if (RUN_SCRIPT_ERROR) { 31 return Promise.reject(RUN_SCRIPT_ERROR) 32 } 33 34 if (RUN_SCRIPT_EXIT_CODE || RUN_SCRIPT_SIGNAL) { 35 return Promise.reject(Object.assign(new Error('command failed'), { 36 code: RUN_SCRIPT_EXIT_CODE, 37 signal: RUN_SCRIPT_SIGNAL, 38 })) 39 } 40 41 return Promise.resolve({ code: 0, signal: null }) 42 } 43 44 const mock = await mockNpm(t, { 45 mocks: { 46 '@npmcli/package-json': mockPJ, 47 '@npmcli/run-script': mockRunScript, 48 }, 49 config: { 50 shell: 'shell-command', 51 }, 52 }) 53 54 await mock.npm.exec('explore', exec) 55 56 return { 57 ...mock, 58 PJ_CALLED, 59 RUN_SCRIPT_EXEC, 60 output: cleanCwd(mock.joinedOutput()).trim(), 61 } 62} 63 64t.test('basic interactive', async t => { 65 const { 66 output, 67 PJ_CALLED, 68 RUN_SCRIPT_EXEC, 69 } = await mockExplore(t, ['pkg']) 70 71 t.ok(PJ_CALLED.endsWith('/pkg')) 72 t.strictSame(RUN_SCRIPT_EXEC, 'shell-command') 73 t.match(output, /Exploring \{CWD\}\/[\w-_/]+\nType 'exit' or \^D when finished/) 74}) 75 76t.test('interactive tracks exit code', async t => { 77 t.test('code', async t => { 78 const { 79 output, 80 PJ_CALLED, 81 RUN_SCRIPT_EXEC, 82 } = await mockExplore(t, ['pkg'], { RUN_SCRIPT_EXIT_CODE: 99 }) 83 84 t.ok(PJ_CALLED.endsWith('/pkg')) 85 t.strictSame(RUN_SCRIPT_EXEC, 'shell-command') 86 t.match(output, /Exploring \{CWD\}\/[\w-_/]+\nType 'exit' or \^D when finished/) 87 88 t.equal(process.exitCode, 99) 89 }) 90 91 t.test('spawn fail', async t => { 92 const RUN_SCRIPT_ERROR = Object.assign(new Error('glorb'), { 93 code: 33, 94 }) 95 await t.rejects( 96 mockExplore(t, ['pkg'], { RUN_SCRIPT_ERROR }), 97 { message: 'glorb', code: 33 } 98 ) 99 t.equal(process.exitCode, 33) 100 }) 101 102 t.test('spawn fail, 0 exit code', async t => { 103 const RUN_SCRIPT_ERROR = Object.assign(new Error('glorb'), { 104 code: 0, 105 }) 106 await t.rejects( 107 mockExplore(t, ['pkg'], { RUN_SCRIPT_ERROR }), 108 { message: 'glorb', code: 0 } 109 ) 110 t.equal(process.exitCode, 1) 111 }) 112 113 t.test('spawn fail, no exit code', async t => { 114 const RUN_SCRIPT_ERROR = Object.assign(new Error('command failed'), { 115 code: 'EPROBLEM', 116 }) 117 await t.rejects( 118 mockExplore(t, ['pkg'], { RUN_SCRIPT_ERROR }), 119 { message: 'command failed', code: 'EPROBLEM' } 120 ) 121 t.equal(process.exitCode, 1) 122 }) 123}) 124 125t.test('basic non-interactive', async t => { 126 const { 127 output, 128 PJ_CALLED, 129 RUN_SCRIPT_EXEC, 130 } = await mockExplore(t, ['pkg', 'ls']) 131 132 t.ok(PJ_CALLED.endsWith('/pkg')) 133 t.strictSame(RUN_SCRIPT_EXEC, 'ls') 134 135 t.strictSame(output, '') 136}) 137 138t.test('signal fails non-interactive', async t => { 139 await t.rejects( 140 mockExplore(t, ['pkg', 'ls'], { RUN_SCRIPT_SIGNAL: 'SIGPROBLEM' }), 141 { 142 message: 'command failed', 143 signal: 'SIGPROBLEM', 144 } 145 ) 146}) 147 148t.test('usage if no pkg provided', async t => { 149 const noPkg = [ 150 [], 151 ['foo/../..'], 152 ['asdf/..'], 153 ['.'], 154 ['..'], 155 ['../..'], 156 ] 157 158 for (const args of noPkg) { 159 t.test(JSON.stringify(args), async t => { 160 await t.rejects( 161 mockExplore(t, args), 162 'Usage:' 163 ) 164 }) 165 } 166}) 167 168t.test('pkg not installed', async t => { 169 const PJ_ERROR = new Error('plurple') 170 171 await t.rejects( 172 mockExplore(t, ['pkg', 'ls'], { PJ_ERROR }), 173 { message: 'plurple' } 174 ) 175}) 176