npm/workspaces/arborist/test/yarn-lock.js

212 lines
7.5 KiB
JavaScript

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'),
]
fixtures.forEach(fixture => t.test(basename(fixture), t =>
new Arborist({ path: fixture }).loadActual().then(tree => {
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')
})