const t = require('tap') const fs = require('fs') const { resolve } = require('path') const { real: mockNpm } = require('../../fixtures/mock-npm') // Attempt to parse json values in snapshots before // stringifying to remove escaped values like \\" // This also doesn't reorder the keys of the object // like tap does by default which is nice in this case t.formatSnapshot = obj => JSON.stringify( obj, (k, v) => { try { return JSON.parse(v) } catch (_) {} return v }, 2 ) // Run shrinkwrap against a specified testdir with config items // and make some assertions that should always be true. Sets // the results on t.context for use in child tests const shrinkwrap = async (t, testdir = {}, config = {}, mocks = {}) => { const { Npm, logs } = mockNpm(t, mocks) const npm = new Npm() await npm.load() npm.localPrefix = t.testdir(testdir) if (config.lockfileVersion) { npm.config.set('lockfile-version', config.lockfileVersion) } if (config.global) { npm.config.set('global', config.global) } await npm.exec('shrinkwrap', []) const newFile = resolve(npm.localPrefix, 'npm-shrinkwrap.json') const oldFile = resolve(npm.localPrefix, 'package-lock.json') const notices = logs.filter(([title]) => title === 'notice').map(([, , msg]) => msg) const warnings = logs.filter(([title]) => title === 'warn').map(([, , msg]) => msg) t.notOk(fs.existsSync(oldFile), 'package-lock is always deleted') t.same(warnings, [], 'no warnings') t.teardown(() => delete t.context) t.context = { localPrefix: testdir, config, shrinkwrap: JSON.parse(fs.readFileSync(newFile)), logs: notices, } } // Run shrinkwrap against all combinations of existing and config // lockfile versions const shrinkwrapMatrix = async (t, file, assertions) => { const ancient = JSON.stringify({ lockfileVersion: 1 }) const existing = JSON.stringify({ lockfileVersion: 2 }) const upgrade = { lockfileVersion: 3 } const downgrade = { lockfileVersion: 1 } let ancientDir = {} let existingDir = null if (file === 'package-lock') { ancientDir = { 'package-lock.json': ancient } existingDir = { 'package-lock.json': existing } } else if (file === 'npm-shrinkwrap') { ancientDir = { 'npm-shrinkwrap.json': ancient } existingDir = { 'npm-shrinkwrap.json': existing } } else if (file === 'hidden-lockfile') { ancientDir = { node_modules: { '.package-lock.json': ancient } } existingDir = { node_modules: { '.package-lock.json': existing } } } await t.test('ancient', async t => { await shrinkwrap(t, ancientDir) t.match(t.context, assertions.ancient) t.matchSnapshot(t.context) }) await t.test('ancient upgrade', async t => { await shrinkwrap(t, ancientDir, upgrade) t.match(t.context, assertions.ancientUpgrade) t.matchSnapshot(t.context) }) if (existingDir) { await t.test('existing', async t => { await shrinkwrap(t, existingDir) t.match(t.context, assertions.existing) t.matchSnapshot(t.context) }) await t.test('existing upgrade', async t => { await shrinkwrap(t, existingDir, upgrade) t.match(t.context, assertions.existingUpgrade) t.matchSnapshot(t.context) }) await t.test('existing downgrade', async t => { await shrinkwrap(t, existingDir, downgrade) t.match(t.context, assertions.existingDowngrade) t.matchSnapshot(t.context) }) } } const NOTICES = { CREATED: (v = '') => [`created a lockfile as npm-shrinkwrap.json${v && ` with version ${v}`}`], RENAMED: (v = '') => [ `package-lock.json has been renamed to npm-shrinkwrap.json${ v && ` and updated to version ${v}` }`, ], UPDATED: (v = '') => [`npm-shrinkwrap.json updated to version ${v}`], SAME: () => [`npm-shrinkwrap.json up to date`], } t.test('with nothing', t => shrinkwrapMatrix(t, null, { ancient: { shrinkwrap: { lockfileVersion: 2 }, logs: NOTICES.CREATED(2), }, ancientUpgrade: { shrinkwrap: { lockfileVersion: 3 }, logs: NOTICES.CREATED(3), }, }) ) t.test('with package-lock.json', t => shrinkwrapMatrix(t, 'package-lock', { ancient: { shrinkwrap: { lockfileVersion: 2 }, logs: NOTICES.RENAMED(2), }, ancientUpgrade: { shrinkwrap: { lockfileVersion: 3 }, logs: NOTICES.RENAMED(3), }, existing: { shrinkwrap: { lockfileVersion: 2 }, logs: NOTICES.RENAMED(), }, existingUpgrade: { shrinkwrap: { lockfileVersion: 3 }, logs: NOTICES.RENAMED(3), }, existingDowngrade: { shrinkwrap: { lockfileVersion: 1 }, logs: NOTICES.RENAMED(1), }, }) ) t.test('with npm-shrinkwrap.json', t => shrinkwrapMatrix(t, 'npm-shrinkwrap', { ancient: { shrinkwrap: { lockfileVersion: 2 }, logs: NOTICES.UPDATED(2), }, ancientUpgrade: { shrinkwrap: { lockfileVersion: 3 }, logs: NOTICES.UPDATED(3), }, existing: { shrinkwrap: { lockfileVersion: 2 }, logs: NOTICES.SAME(), }, existingUpgrade: { shrinkwrap: { lockfileVersion: 3 }, logs: NOTICES.UPDATED(3), }, existingDowngrade: { shrinkwrap: { lockfileVersion: 1 }, logs: NOTICES.UPDATED(1), }, }) ) t.test('with hidden lockfile', t => shrinkwrapMatrix(t, 'hidden-lockfile', { ancient: { shrinkwrap: { lockfileVersion: 1 }, logs: NOTICES.CREATED(), }, ancientUpgrade: { shrinkwrap: { lockfileVersion: 3 }, logs: NOTICES.CREATED(), }, existing: { shrinkwrap: { lockfileVersion: 2 }, logs: NOTICES.CREATED(), }, existingUpgrade: { shrinkwrap: { lockfileVersion: 3 }, logs: NOTICES.CREATED(3), }, existingDowngrade: { shrinkwrap: { lockfileVersion: 1 }, logs: NOTICES.CREATED(1), }, }) ) t.test('throws in global mode', async t => { t.rejects( shrinkwrap( t, {}, { global: true, } ), { message: '`npm shrinkwrap` does not work for global packages', code: 'ESHRINKWRAPGLOBAL', } ) })