const YarnLock = require('../lib/yarn-lock.js') const Arborist = require('../lib/arborist') const Node = require('../lib/node.js') const t = require('tap') const { resolve, basename } = require('path') const fixtures = [ resolve(__dirname, 'fixtures/tap-with-yarn-lock'), resolve(__dirname, 'fixtures/yarn-stuff'), ] const { readFileSync } = require('fs') fixtures.forEach(f => t.test(basename(f), t => { const lockdata = readFileSync(f + '/yarn.lock') const yarnLock = new YarnLock() // parse the data yarnLock.parse(lockdata) // then turn it into output const lockOutput = yarnLock.toString() // snapshot the result t.matchSnapshot(lockOutput, 'generated output from input') const yarnLock2 = new YarnLock() yarnLock2.parse(lockOutput) t.strictSame(yarnLock2, yarnLock, 'same parsed result from string output') t.end() })) t.test('invalid yarn lockfile data throws', t => { t.throws(() => YarnLock.parse(` asdf@foo: this !is not vlid i mean what even is it?? not yarn lock, that's for sure {"maybe":"json"}? - or: even - yaml? - NO `), { content: ' this !is not vlid\n', line: 3, position: 11 }, 'just garbage') t.throws(() => YarnLock.parse(` asdf@foo: dependencies: foo bar baz blork `), { content: ' foo bar baz blork\n', line: 4 }, 'invalid subkey') t.end() }) t.test('omits empty dependency list on toString output', t => { const y = new YarnLock() y.parse(` foo@bar: version "1.2.3" resolved "https://registry.local/foo/-/foo-1.2.3.tgz" dependencies: # Note: do not require a \\n at the end of the file, just add it if missing # Also: comments are not preserved. bar@foo: version "1.2.3"`) t.equal(y.toString(), `# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. # yarn lockfile v1 "bar@foo": "version" "1.2.3" "foo@bar": "resolved" "https://registry.local/foo/-/foo-1.2.3.tgz" "version" "1.2.3" `) t.end() }) t.test('exports YarnLockEntry class', t => { t.type(YarnLock.Entry, 'function') t.end() }) t.test('load a yarn lock from an actual tree', t => { // make sure the symlinks are loaded require('./fixtures/index.js') const fixtures = [ resolve(__dirname, 'fixtures/install-types'), resolve(__dirname, 'fixtures/links-all-over'), ] for (const fixture of fixtures) { t.test(basename(fixture), async t => { const tree = await new Arborist({ path: fixture }).loadActual() const y = YarnLock.fromTree(tree) t.matchSnapshot(y.toString(), 'yarn.lock from a package tree') }) } t.end() }) t.test('yarn lock with dedupes yarn wouldnt do', async t => { const tree = new Node({ path: '/my/project', pkg: { dependencies: { x: '1.x', y: '1.x', z: '1.x' } }, children: [ { pkg: { name: 'x', version: '1.2.0' } }, { pkg: { name: 'y', version: '1.0.0', dependencies: { x: '1.1', z: '2.x' } }, children: [ { pkg: { name: 'x', version: '1.1.0' } }, { pkg: { name: 'z', version: '2.0.0', dependencies: { x: '1.x' } } }, // note: yarn nests another x@1.2.0 here, because it locks // the 1.x resolution to 1.2.0 even when unnecessary ], }, { pkg: { name: 'z', version: '1.0.0' } }, ], }) const y = YarnLock.fromTree(tree) t.matchSnapshot(y.toString(), 'yarn.lock from deduped tree') }) t.test('deduped prior entries that dont match one another', async t => { const tree = new Node({ path: '/my/project', pkg: { dependencies: { a: '', b: '' } }, children: [ { pkg: { name: 'a', dependencies: { i: '', x: '1.x', y: '1.x', z: '1.x' } }, children: [ { pkg: { name: 'i', version: '1.0.0', dependencies: { x: '1.2.0' } } }, { pkg: { name: 'x', version: '1.2.0' }, integrity: 'x120' }, { pkg: { name: 'y', version: '1.0.0', dependencies: { x: '1.1', z: '2.x' } }, children: [ { pkg: { name: 'x', version: '1.1.0' } }, { pkg: { name: 'z', version: '2.0.0', dependencies: { x: '1.x' } } }, ], }, { pkg: { name: 'z', version: '1.0.0' } }, ], }, { pkg: { name: 'b', dependencies: { j: '', x: '1.x', y: '1.x', z: '1.x' } }, children: [ { pkg: { name: 'j', version: '1.0.0', dependencies: { x: '1.3.0' } } }, { pkg: { name: 'x', version: '1.3.0' }, integrity: 'x130' }, { pkg: { name: 'y', version: '1.0.0', dependencies: { x: '1.1', z: '2.x' } }, children: [ { pkg: { name: 'x', version: '1.1.0' } }, { pkg: { name: 'z', version: '2.0.0', dependencies: { x: '1.x' } } }, ], }, { pkg: { name: 'z', version: '1.0.0' } }, ], }, ], }) const y = YarnLock.fromTree(tree) t.matchSnapshot(y.toString(), 'yarn.lock with mismatching previous resolutions') }) t.test('more nesting tree complications', async t => { const tree = new Node({ path: '/my/project', pkg: { dependencies: { a: '', b: '', c: '', c2: '', d3: '' } }, children: [ { pkg: { name: 'a', dependencies: { x: '', g: '', h: '', i: '', j: '', k: '' } }, children: [ { pkg: { name: 'x', version: '1.0.0' }, resolved: 'https://x100.xyz' }, { pkg: { name: 'g', version: '1.0.0', dependencies: { x: '^1.0.0' } } }, { pkg: { name: 'h', version: '1.0.0', dependencies: { x: '1.0.0' } } }, { pkg: { name: 'i', version: '1.0.0', dependencies: { x: '1.0' } } }, { pkg: { name: 'j', version: '1.0.0', dependencies: { x: '1' } } }, { pkg: { name: 'k', version: '1.0.0', dependencies: { x: '' } } }, ], }, // previously-seen specs that don't match { pkg: { name: 'b', dependencies: { x: '', l: '', m: '', n: '', n2: '', o: '', p: '' } }, children: [ { pkg: { name: 'x', version: '1.0.1' } }, { pkg: { name: 'l', version: '1.0.0', dependencies: { x: '^1.0.1' } } }, { pkg: { name: 'm', version: '1.0.0', dependencies: { x: '1.0.1' } } }, { pkg: { name: 'n', version: '1.0.0', dependencies: { x: '1.0' } } }, { pkg: { name: 'n2', version: '1.0.0', dependencies: { x: '>=1.0' } } }, { pkg: { name: 'o', version: '1.0.0', dependencies: { x: '1' } } }, { pkg: { name: 'p', version: '1.0.0', dependencies: { x: '' } } }, ], }, // new specs that get added right away to the entry { pkg: { name: 'c', dependencies: { d: '', e: '', f: '' } }, children: [ { pkg: { name: 'x', version: '1.0.1' }, integrity: 'x101', resolved: 'https://x101.xyz' }, { pkg: { name: 'd', version: '1.0.0', dependencies: { x: '^1.0.1' } } }, { pkg: { name: 'e', version: '1.0.0', dependencies: { x: '>=1.0.1 <2' } } }, { pkg: { name: 'f', version: '1.0.0', dependencies: { x: '>=1.0' } } }, ], }, // new specs that later match { pkg: { name: 'c2', dependencies: { d2: '', e2: '', f2: '' } }, children: [ { pkg: { name: 'x', version: '1.0.1' }, resolved: 'https://x101.xyz' }, { pkg: { name: 'd2', version: '1.0.0', dependencies: { x: '^1.0.0-x' } } }, { pkg: { name: 'e2', version: '1.0.0', dependencies: { x: '>=1.0.0' } } }, { pkg: { name: 'f2', version: '1.0.0', dependencies: { x: '1.0.1' } } }, ], }, // no version, resolved, or integrity, we assume a match { pkg: { name: 'd3', dependencies: { x: '' } }, children: [{ pkg: { name: 'x' } }], }, ], }) const y = YarnLock.fromTree(tree) t.matchSnapshot(y.toString(), 'yarn.lock with mismatching previous resolutions') })