Skip to content
This repository has been archived by the owner on Jan 20, 2022. It is now read-only.

Commit

Permalink
Respect hidden lockfiles when symlink deps present
Browse files Browse the repository at this point in the history
As long as the target of the link is in the shrinkwrap, and not newer
  • Loading branch information
isaacs committed Mar 9, 2021
1 parent b8cb67d commit 8ef1988
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 4 deletions.
18 changes: 14 additions & 4 deletions lib/shrinkwrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const readFile = promisify(fs.readFile)
const writeFile = promisify(fs.writeFile)
const stat = promisify(fs.stat)
const readdir_ = promisify(fs.readdir)
const readlink = promisify(fs.readlink)

// XXX remove when drop support for node v10
const lstat = promisify(fs.lstat)
Expand Down Expand Up @@ -176,10 +177,19 @@ const assertNoNewer = async (path, data, lockTime, dir = path, seen = null) => {
: readdir(parent, { withFileTypes: true })

return children.catch(() => [])
.then(ents => Promise.all(
ents.filter(ent => ent.isDirectory() && !/^\./.test(ent.name))
.map(ent => assertNoNewer(path, data, lockTime, resolve(parent, ent.name), seen))
)).then(() => {
.then(ents => Promise.all(ents.map(async ent => {
const child = resolve(parent, ent.name)
if (ent.isDirectory() && !/^\./.test(ent.name))
await assertNoNewer(path, data, lockTime, child, seen)
else if (ent.isSymbolicLink()) {
const target = resolve(parent, await readlink(child))
const tstat = await stat(target).catch(() => null)
seen.add(relpath(path, child))
if (tstat && tstat.isDirectory() && !seen.has(relpath(path, target)))
await assertNoNewer(path, data, lockTime, target, seen)
}
})))
.then(() => {
if (dir !== path)
return

Expand Down
75 changes: 75 additions & 0 deletions test/shrinkwrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -703,13 +703,15 @@ t.test('hidden lockfile only used if up to date', async t => {
},
'package.json': JSON.stringify({ dependencies: { abbrev: '1.1.1' }}),
})

// ensure that the lockfile is fresh to start
{
const later = Date.now() + 10000
fs.utimesSync(resolve(path, hidden), new Date(later), new Date(later))
const s = await Shrinkwrap.load({ path, hiddenLockfile: true })
t.equal(s.loadedFromDisk, true, 'loading from fresh lockfile')
}

// make the node_modules dir have a newer mtime by adding an entry
// and setting the hidden lockfile back in time
{
Expand All @@ -720,6 +722,7 @@ t.test('hidden lockfile only used if up to date', async t => {
t.equal(s.loadedFromDisk, false, 'did not load from disk, updated nm')
t.equal(s.loadingError, 'out of date, updated: node_modules')
}

// make the lockfile newer, but that new entry is still a problem
{
const later = Date.now() + 10000
Expand All @@ -728,6 +731,7 @@ t.test('hidden lockfile only used if up to date', async t => {
t.equal(s.loadedFromDisk, false, 'did not load, new entry')
t.equal(s.loadingError, 'missing from lockfile: node_modules/xyz')
}

// make the lockfile newer, but missing a folder from node_modules
{
rimraf.sync(resolve(path, 'node_modules/abbrev'))
Expand All @@ -740,6 +744,77 @@ t.test('hidden lockfile only used if up to date', async t => {
}
})

t.test('hidden lockfile understands symlinks', async t => {
const path = t.testdir({
node_modules: {
'.package-lock.json': JSON.stringify({
name: 'hidden-lockfile-with-symlink',
lockfileVersion: 2,
requires: true,
packages: {
abbrev: {
version: '1.1.1',
},
'node_modules/abbrev': {
resolved: 'abbrev',
link: true,
},
},
}),
abbrev: t.fixture('symlink', '../abbrev'),

// a symlink missing a target is not relevant
missing: t.fixture('symlink', '../missing'),
},
abbrev: {
'package.json': JSON.stringify({
name: 'abbrev',
version: '1.1.1',
}),
},
'package.json': JSON.stringify({
dependencies: {
abbrev: 'file:abbrev',
},
}),
})

// respect it if not newer, and the target included in shrinkwrap
{
const later = Date.now() + 10000
fs.utimesSync(resolve(path, hidden), new Date(later), new Date(later))
const s = await Shrinkwrap.load({ path, hiddenLockfile: true })
t.equal(s.loadedFromDisk, true, 'loaded from disk')
t.equal(s.filename, resolve(path, hidden))
}

// don't respect if the target is newer than hidden shrinkwrap
{
const later = Date.now() + 20000
fs.utimesSync(resolve(path, 'abbrev/package.json'), new Date(later), new Date(later))
fs.utimesSync(resolve(path, 'abbrev'), new Date(later), new Date(later))
const s = await Shrinkwrap.load({ path, hiddenLockfile: true })
t.equal(s.loadedFromDisk, false, 'not loaded from disk')
t.equal(s.filename, resolve(path, hidden))
}

// don't respect it if the target is not in hidden shrinkwrap
{
fs.mkdirSync(resolve(path, 'missing'))
fs.writeFileSync(resolve(path, 'missing/package.json'), JSON.stringify({
name: 'missing',
version: '1.2.3',
}))
// even though it's newer, the 'missing' is not found in the lock
const later = Date.now() + 30000
fs.utimesSync(resolve(path, hidden), new Date(later), new Date(later))

const s = await Shrinkwrap.load({ path, hiddenLockfile: true })
t.equal(s.loadedFromDisk, false, 'not loaded from disk')
t.equal(s.filename, resolve(path, hidden))
}
})

t.test('a yarn.lock entry with version mismatch', async t => {
const path = t.testdir({
'yarn.lock': `
Expand Down

0 comments on commit 8ef1988

Please sign in to comment.