Skip to content

Commit 815311b

Browse files
committed
fix(arborist): workspaces correctly path to file: packages from overrides
1 parent 5bd086b commit 815311b

File tree

2 files changed

+90
-1
lines changed

2 files changed

+90
-1
lines changed

workspaces/arborist/lib/arborist/reify.js

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -825,7 +825,14 @@ module.exports = cls => class Reifier extends cls {
825825
// symlink
826826
const dir = dirname(node.path)
827827
const target = node.realpath
828-
const rel = relative(dir, target)
828+
829+
let rel
830+
if (node.resolved?.startsWith('file:')) {
831+
rel = this.#calculateRelativePath(node, dir, target, nm)
832+
} else {
833+
rel = relative(dir, target)
834+
}
835+
829836
await mkdir(dir, { recursive: true })
830837
return symlink(rel, node.path, 'junction')
831838
}
@@ -843,6 +850,36 @@ module.exports = cls => class Reifier extends cls {
843850
}) : p).then(() => node)
844851
}
845852

853+
#calculateRelativePath (node, dir, target) {
854+
// Check if the node is affected by a root override
855+
let hasRootOverride = [...node.edgesIn].some(edge => edge.from.isRoot && edge.overrides)
856+
// If not set via edges, see if the root package.json explicitly lists an override
857+
if (!hasRootOverride && node.root) {
858+
const rootPackage = node.root.target
859+
hasRootOverride = !!(rootPackage &&
860+
rootPackage.package.overrides &&
861+
rootPackage.package.overrides[node.name])
862+
}
863+
if (!hasRootOverride) {
864+
return relative(dir, target)
865+
}
866+
// If an override is detected, attempt to retrieve the override spec from the root package.json
867+
const overrideSpec = node.root?.target?.package?.overrides?.[node.name]
868+
if (typeof overrideSpec === 'string' && overrideSpec.startsWith('file:')) {
869+
const overridePath = overrideSpec.replace(/^file:/, '')
870+
const rootDir = node.root.target.path
871+
return relative(dir, resolve(rootDir, overridePath))
872+
}
873+
874+
// Fallback: derive the package name from node.resolved in a platform-agnostic way
875+
const filePath = node.resolved.replace(/^file:/, '')
876+
// A node.package.name could be different than the folder name
877+
const pathParts = filePath.split(/[\\/]/)
878+
const packageName = pathParts[pathParts.length - 1]
879+
880+
return join('..', packageName)
881+
}
882+
846883
#registryResolved (resolved) {
847884
// the default registry url is a magic value meaning "the currently
848885
// configured registry".

workspaces/arborist/test/arborist/reify.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const fs = require('node:fs')
88
const fsp = require('node:fs/promises')
99
const npmFs = require('@npmcli/fs')
1010
const MockRegistry = require('@npmcli/mock-registry')
11+
const { dirname, relative } = require('node:path')
1112

1213
let failRm = false
1314
let failRename = null
@@ -3204,6 +3205,57 @@ t.test('installLinks', (t) => {
32043205
t.end()
32053206
})
32063207

3208+
t.test('root overrides with file: paths are visible to workspaces', async t => {
3209+
const path = t.testdir({
3210+
'package.json': JSON.stringify({
3211+
name: 'root',
3212+
workspaces: ['hello'],
3213+
dependencies: {},
3214+
overrides: {
3215+
print: 'file:./print',
3216+
},
3217+
}),
3218+
hello: {
3219+
'package.json': JSON.stringify({
3220+
name: 'hello',
3221+
version: '1.0.0',
3222+
dependencies: {
3223+
print: '../print',
3224+
},
3225+
}),
3226+
},
3227+
print: {
3228+
'package.json': JSON.stringify({
3229+
name: 'print',
3230+
version: '1.0.0',
3231+
main: 'index.js',
3232+
}),
3233+
},
3234+
})
3235+
3236+
createRegistry(t, false)
3237+
await reify(path)
3238+
3239+
const printSymlink = fs.readlinkSync(resolve(path, 'node_modules/print'))
3240+
3241+
// Create a platform-agnostic way to compare symlink targets
3242+
const normalizeLinkTarget = target => {
3243+
if (process.platform === 'win32') {
3244+
// For Windows: convert absolute paths to relative and normalize slashes
3245+
const linkDir = dirname(resolve(path, 'node_modules/print'))
3246+
return relative(linkDir, target).replace(/\\/g, '/')
3247+
}
3248+
// For Unix: already a relative path
3249+
return target
3250+
}
3251+
3252+
t.equal(
3253+
normalizeLinkTarget(printSymlink),
3254+
'../print',
3255+
'print symlink points to ../print (normalized for platform)'
3256+
)
3257+
})
3258+
32073259
t.test('should preserve exact ranges, missing actual tree', async (t) => {
32083260
const Pacote = require('pacote')
32093261
const Arborist = t.mock('../../lib/arborist', {

0 commit comments

Comments
 (0)