@@ -1979,6 +1979,53 @@ tap.test('orphaned store entries are cleaned up on dependency removal', async t
19791979 'all store entries are removed when dependencies are removed' )
19801980} )
19811981
1982+ tap . test ( 'store symlinks are updated when hash changes after adding a dep' , async t => {
1983+ const graph = {
1984+ registry : [
1985+ { name : 'which' , version : '1.0.0' , dependencies : { isexe : '^1.0.0' } } ,
1986+ { name : 'isexe' , version : '1.0.0' } ,
1987+ { name : 'abbrev' , version : '2.0.0' } ,
1988+ ] ,
1989+ root : {
1990+ name : 'myproject' ,
1991+ version : '1.0.0' ,
1992+ dependencies : { which : '1.0.0' } ,
1993+ } ,
1994+ }
1995+ const { dir, registry } = await getRepo ( graph )
1996+ const cache = fs . mkdtempSync ( `${ getTempDir ( ) } /test-` )
1997+
1998+ // First install — only which
1999+ const arb1 = new Arborist ( { path : dir , registry, packumentCache : new Map ( ) , cache } )
2000+ await arb1 . reify ( { installStrategy : 'linked' } )
2001+
2002+ const whichLink = path . join ( dir , 'node_modules' , 'which' )
2003+ t . ok ( fs . lstatSync ( whichLink ) . isSymbolicLink ( ) , 'which is a symlink after first install' )
2004+ const hashBefore = fs . readlinkSync ( whichLink )
2005+
2006+ // Add abbrev — changes the dep graph, causing store hash recalculation
2007+ const pkgPath = path . join ( dir , 'package.json' )
2008+ const pkg = JSON . parse ( fs . readFileSync ( pkgPath , 'utf8' ) )
2009+ pkg . dependencies . abbrev = '2.0.0'
2010+ fs . writeFileSync ( pkgPath , JSON . stringify ( pkg ) )
2011+
2012+ const arb2 = new Arborist ( { path : dir , registry, packumentCache : new Map ( ) , cache } )
2013+ await arb2 . reify ( { installStrategy : 'linked' } )
2014+
2015+ // The symlink target should still be valid (not dangling)
2016+ t . ok ( fs . existsSync ( whichLink ) , 'which symlink target exists after adding abbrev' )
2017+ t . ok ( setupRequire ( dir ) ( 'which' ) , 'which is requireable after adding abbrev' )
2018+
2019+ // Verify the symlink was updated if the hash changed
2020+ const hashAfter = fs . readlinkSync ( whichLink )
2021+ if ( hashBefore !== hashAfter ) {
2022+ const storeDir = path . join ( dir , 'node_modules' , '.store' )
2023+ const storeEntries = fs . readdirSync ( storeDir )
2024+ const oldKey = hashBefore . split ( '/' ) [ 0 ] . replace ( '.store/' , '' )
2025+ t . notOk ( storeEntries . includes ( oldKey ) , 'old store entry was cleaned up' )
2026+ }
2027+ } )
2028+
19822029function setupRequire ( cwd ) {
19832030 return function requireChain ( ...chain ) {
19842031 return chain . reduce ( ( path , name ) => {
0 commit comments