// materializer version-resolution — the load-bearing fix that keeps a
// multi-version closure from collapsing onto a single dep version.
//
// Regression: the node materializer indexed deps by NAME only (last-writer-
// wins), so when a closure carried compact-encoding 2.19.2 AND 3.2.0, every
// dependent got the same one. That paired protomux@3.11.0 (needs ce ^3.0.0,
// uses c.optionalBuffer) with ce@2.19.2 (no optionalBuffer) → crash, and
// protomux@3.10.3 with ce@3.2.0 → the original DeployServer crash. The fix
// resolves each edge against the requirer's own range.
import test from 'brittle'
import { pickHash, satisfies, parseVersion, compareParsed } from '../lib/materializers/node.js'
test('satisfies — caret locks the major (the protomux/ce case)', (t) => {
t.ok(satisfies(parseVersion('3.2.0'), '^3.0.0'), '3.2.0 satisfies ^3.0.0')
t.absent(satisfies(parseVersion('2.19.2'), '^3.0.0'), '2.19.2 does NOT satisfy ^3.0.0')
t.ok(satisfies(parseVersion('2.19.2'), '^2.0.0'), '2.19.2 satisfies ^2.0.0')
t.absent(satisfies(parseVersion('3.2.0'), '^2.0.0'), '3.2.0 does NOT satisfy ^2.0.0')
t.ok(satisfies(parseVersion('2.19.2'), '^2.11.0'), '2.19.2 satisfies ^2.11.0 (hypercore@11.28.1)')
})
test('satisfies — caret 0.x locks the minor / patch', (t) => {
t.ok(satisfies(parseVersion('0.2.5'), '^0.2.1'), '0.2.5 satisfies ^0.2.1')
t.absent(satisfies(parseVersion('0.3.0'), '^0.2.1'), '0.3.0 does NOT satisfy ^0.2.1')
t.ok(satisfies(parseVersion('0.0.5'), '^0.0.5'), '0.0.5 satisfies ^0.0.5')
t.absent(satisfies(parseVersion('0.0.6'), '^0.0.5'), '0.0.6 does NOT satisfy ^0.0.5')
})
test('satisfies — tilde, exact, comparators, wildcards, non-semver', (t) => {
t.ok(satisfies(parseVersion('1.2.9'), '~1.2.3'), '1.2.9 ~1.2.3')
t.absent(satisfies(parseVersion('1.3.0'), '~1.2.3'), '1.3.0 not ~1.2.3')
t.ok(satisfies(parseVersion('1.2.3'), '1.2.3'), 'exact match')
t.absent(satisfies(parseVersion('1.2.4'), '1.2.3'), 'exact mismatch')
t.ok(satisfies(parseVersion('4.17.0'), '>=4.11.0'), '>= comparator')
t.absent(satisfies(parseVersion('4.10.0'), '>=4.11.0'), '>= comparator fail')
t.ok(satisfies(parseVersion('9.9.9'), '*'), '* matches anything')
t.ok(satisfies(parseVersion('9.9.9'), ''), 'empty range matches anything')
t.ok(satisfies(parseVersion('1.0.0'), 'file:../foo'), 'file: range → match (fallback to highest)')
t.ok(satisfies(parseVersion('1.0.0'), '^1.0.0 || ^2.0.0'), 'union — first clause')
t.ok(satisfies(parseVersion('2.5.0'), '^1.0.0 || ^2.0.0'), 'union — second clause')
})
test('pickHash — protomux@^3 and hypercore@^2 resolve DIFFERENT ce from one closure', (t) => {
// Closure carries both ce versions (the exact real scenario).
const closure = new Map([
['compact-encoding', [
{ version: '2.19.2', tree: 'CE2' },
{ version: '3.2.0', tree: 'CE3' }
]]
])
t.is(pickHash(closure, 'compact-encoding', '^3.0.0'), 'CE3', 'protomux 3.11.0 → ce 3.2.0')
t.is(pickHash(closure, 'compact-encoding', '^2.0.0'), 'CE2', 'old line → ce 2.19.2')
t.is(pickHash(closure, 'compact-encoding', '^2.11.0'), 'CE2', 'hypercore 11.28.1 → ce 2.19.2')
})
test('pickHash — single version short-circuits regardless of range', (t) => {
const closure = new Map([['b4a', [{ version: '1.6.7', tree: 'B4A' }]]])
t.is(pickHash(closure, 'b4a', '^1.6.0'), 'B4A')
t.is(pickHash(closure, 'b4a', 'whatever-nonsemver'), 'B4A')
})
test('pickHash — no satisfying version falls back to highest present', (t) => {
const closure = new Map([['x', [
{ version: '1.0.0', tree: 'X1' },
{ version: '2.5.0', tree: 'X2' }
]]])
// Range satisfied by none in closure → highest available, deterministically.
t.is(pickHash(closure, 'x', '^3.0.0'), 'X2')
})
test('pickHash — picks the HIGHEST satisfying version', (t) => {
const closure = new Map([['x', [
{ version: '3.0.0', tree: 'X30' },
{ version: '3.2.0', tree: 'X32' },
{ version: '3.1.0', tree: 'X31' }
]]])
t.is(pickHash(closure, 'x', '^3.0.0'), 'X32', 'highest of the satisfying set')
})
test('pickHash — absent name → null', (t) => {
t.is(pickHash(new Map(), 'nope', '^1.0.0'), null)
})
test('compareParsed orders by major.minor.patch', (t) => {
t.ok(compareParsed(parseVersion('3.2.0'), parseVersion('3.1.9')) > 0)
t.ok(compareParsed(parseVersion('2.19.2'), parseVersion('3.0.0')) < 0)
t.is(compareParsed(parseVersion('1.2.3'), parseVersion('1.2.3')), 0)
})