1const t = require('tap') 2const fs = require('fs') 3const { resolve } = require('path') 4const { load: loadMockNpm } = require('../../fixtures/mock-npm') 5 6// Attempt to parse json values in snapshots before 7// stringifying to remove escaped values like \\" 8// This also doesn't reorder the keys of the object 9// like tap does by default which is nice in this case 10t.formatSnapshot = obj => 11 JSON.stringify( 12 obj, 13 (k, v) => { 14 try { 15 return JSON.parse(v) 16 } catch { 17 // leave invalid JSON as a string 18 } 19 return v 20 }, 21 2 22 ) 23 24// Run shrinkwrap against a specified prefixDir with config items 25// and make some assertions that should always be true. Sets 26// the results on t.context for use in child tests 27const shrinkwrap = async (t, prefixDir = {}, config = {}, mocks = {}) => { 28 const { npm, logs } = await loadMockNpm(t, { 29 mocks, 30 config, 31 prefixDir, 32 }) 33 34 await npm.exec('shrinkwrap', []) 35 36 const newFile = resolve(npm.prefix, 'npm-shrinkwrap.json') 37 const oldFile = resolve(npm.prefix, 'package-lock.json') 38 39 t.notOk(fs.existsSync(oldFile), 'package-lock is always deleted') 40 t.same(logs.warn, [], 'no warnings') 41 t.teardown(() => delete t.context) 42 t.context = { 43 localPrefix: prefixDir, 44 config, 45 shrinkwrap: JSON.parse(fs.readFileSync(newFile)), 46 logs: logs.notice.map(([, m]) => m), 47 } 48} 49 50// Run shrinkwrap against all combinations of existing and config 51// lockfile versions 52const shrinkwrapMatrix = async (t, file, assertions) => { 53 const ancient = JSON.stringify({ lockfileVersion: 1 }) 54 const existing = JSON.stringify({ lockfileVersion: 2 }) 55 const upgrade = { 'lockfile-version': 3 } 56 const downgrade = { 'lockfile-version': 1 } 57 58 let ancientDir = {} 59 let existingDir = null 60 if (file === 'package-lock') { 61 ancientDir = { 'package-lock.json': ancient } 62 existingDir = { 'package-lock.json': existing } 63 } else if (file === 'npm-shrinkwrap') { 64 ancientDir = { 'npm-shrinkwrap.json': ancient } 65 existingDir = { 'npm-shrinkwrap.json': existing } 66 } else if (file === 'hidden-lockfile') { 67 ancientDir = { node_modules: { '.package-lock.json': ancient } } 68 existingDir = { node_modules: { '.package-lock.json': existing } } 69 } 70 71 await t.test('ancient', async t => { 72 await shrinkwrap(t, ancientDir) 73 t.match(t.context, assertions.ancient) 74 t.matchSnapshot(t.context) 75 }) 76 await t.test('ancient upgrade', async t => { 77 await shrinkwrap(t, ancientDir, upgrade) 78 t.match(t.context, assertions.ancientUpgrade) 79 t.matchSnapshot(t.context) 80 }) 81 82 if (existingDir) { 83 await t.test('existing', async t => { 84 await shrinkwrap(t, existingDir) 85 t.match(t.context, assertions.existing) 86 t.matchSnapshot(t.context) 87 }) 88 await t.test('existing upgrade', async t => { 89 await shrinkwrap(t, existingDir, upgrade) 90 t.match(t.context, assertions.existingUpgrade) 91 t.matchSnapshot(t.context) 92 }) 93 await t.test('existing downgrade', async t => { 94 await shrinkwrap(t, existingDir, downgrade) 95 t.match(t.context, assertions.existingDowngrade) 96 t.matchSnapshot(t.context) 97 }) 98 } 99} 100 101const NOTICES = { 102 CREATED: (v = '') => [`created a lockfile as npm-shrinkwrap.json${v && ` with version ${v}`}`], 103 RENAMED: (v = '') => [ 104 `package-lock.json has been renamed to npm-shrinkwrap.json${ 105 v && ` and updated to version ${v}` 106 }`, 107 ], 108 UPDATED: (v = '') => [`npm-shrinkwrap.json updated to version ${v}`], 109 SAME: () => [`npm-shrinkwrap.json up to date`], 110} 111 112t.test('with nothing', t => 113 shrinkwrapMatrix(t, null, { 114 ancient: { 115 shrinkwrap: { lockfileVersion: 3 }, 116 logs: NOTICES.CREATED(3), 117 }, 118 ancientUpgrade: { 119 shrinkwrap: { lockfileVersion: 3 }, 120 logs: NOTICES.CREATED(3), 121 }, 122 }) 123) 124 125t.test('with package-lock.json', t => 126 shrinkwrapMatrix(t, 'package-lock', { 127 ancient: { 128 shrinkwrap: { lockfileVersion: 3 }, 129 logs: NOTICES.RENAMED(3), 130 }, 131 ancientUpgrade: { 132 shrinkwrap: { lockfileVersion: 3 }, 133 logs: NOTICES.RENAMED(3), 134 }, 135 existing: { 136 shrinkwrap: { lockfileVersion: 2 }, 137 logs: NOTICES.RENAMED(), 138 }, 139 existingUpgrade: { 140 shrinkwrap: { lockfileVersion: 3 }, 141 logs: NOTICES.RENAMED(3), 142 }, 143 existingDowngrade: { 144 shrinkwrap: { lockfileVersion: 1 }, 145 logs: NOTICES.RENAMED(1), 146 }, 147 }) 148) 149 150t.test('with npm-shrinkwrap.json', t => 151 shrinkwrapMatrix(t, 'npm-shrinkwrap', { 152 ancient: { 153 shrinkwrap: { lockfileVersion: 3 }, 154 logs: NOTICES.UPDATED(3), 155 }, 156 ancientUpgrade: { 157 shrinkwrap: { lockfileVersion: 3 }, 158 logs: NOTICES.UPDATED(3), 159 }, 160 existing: { 161 shrinkwrap: { lockfileVersion: 2 }, 162 logs: NOTICES.SAME(), 163 }, 164 existingUpgrade: { 165 shrinkwrap: { lockfileVersion: 3 }, 166 logs: NOTICES.UPDATED(3), 167 }, 168 existingDowngrade: { 169 shrinkwrap: { lockfileVersion: 1 }, 170 logs: NOTICES.UPDATED(1), 171 }, 172 }) 173) 174 175t.test('with hidden lockfile', t => 176 shrinkwrapMatrix(t, 'hidden-lockfile', { 177 ancient: { 178 shrinkwrap: { lockfileVersion: 1 }, 179 logs: NOTICES.CREATED(), 180 }, 181 ancientUpgrade: { 182 shrinkwrap: { lockfileVersion: 3 }, 183 logs: NOTICES.CREATED(), 184 }, 185 existing: { 186 shrinkwrap: { lockfileVersion: 2 }, 187 logs: NOTICES.CREATED(), 188 }, 189 existingUpgrade: { 190 shrinkwrap: { lockfileVersion: 3 }, 191 logs: NOTICES.CREATED(3), 192 }, 193 existingDowngrade: { 194 shrinkwrap: { lockfileVersion: 1 }, 195 logs: NOTICES.CREATED(1), 196 }, 197 }) 198) 199 200t.test('throws in global mode', async t => { 201 t.rejects( 202 shrinkwrap( 203 t, 204 {}, 205 { 206 global: true, 207 } 208 ), 209 { 210 message: '`npm shrinkwrap` does not work for global packages', 211 code: 'ESHRINKWRAPGLOBAL', 212 } 213 ) 214}) 215