@@ -40,6 +40,7 @@ const debug = require('./debug.js')
40
40
const gatherDepSet = require ( './gather-dep-set.js' )
41
41
const treeCheck = require ( './tree-check.js' )
42
42
const { walkUp } = require ( 'walk-up-path' )
43
+ const { log } = require ( 'proc-log' )
43
44
44
45
const { resolve, relative, dirname, basename } = require ( 'node:path' )
45
46
const util = require ( 'node:util' )
@@ -344,7 +345,28 @@ class Node {
344
345
}
345
346
346
347
get overridden ( ) {
347
- return ! ! ( this . overrides && this . overrides . value && this . overrides . name === this . name )
348
+ if ( ! this . overrides ) {
349
+ return false
350
+ }
351
+ if ( ! this . overrides . value ) {
352
+ return false
353
+ }
354
+ if ( this . overrides . name !== this . name ) {
355
+ return false
356
+ }
357
+
358
+ // The overrides rule is for a package with this name, but some override rules only apply to specific
359
+ // versions. To make sure this package was actually overridden, we check whether any edge going in
360
+ // had the rule applied to it, in which case its overrides set is different than its source node.
361
+ for ( const edge of this . edgesIn ) {
362
+ if ( edge . overrides && edge . overrides . name === this . name && edge . overrides . value === this . version ) {
363
+ if ( ! edge . overrides . isEqual ( edge . from . overrides ) ) {
364
+ return true
365
+ }
366
+ }
367
+ }
368
+
369
+ return false
348
370
}
349
371
350
372
get package ( ) {
@@ -822,9 +844,6 @@ class Node {
822
844
target . root = root
823
845
}
824
846
825
- if ( ! this . overrides && this . parent && this . parent . overrides ) {
826
- this . overrides = this . parent . overrides . getNodeRule ( this )
827
- }
828
847
// tree should always be valid upon root setter completion.
829
848
treeCheck ( this )
830
849
if ( this !== root ) {
@@ -1006,10 +1025,21 @@ class Node {
1006
1025
return false
1007
1026
}
1008
1027
1009
- // XXX need to check for two root nodes?
1010
- if ( node . overrides !== this . overrides ) {
1011
- return false
1028
+ // If this node has no dependencies, then it's irrelevant to check the override
1029
+ // rules of the replacement node.
1030
+ if ( this . edgesOut . size ) {
1031
+ // XXX need to check for two root nodes?
1032
+ if ( node . overrides ) {
1033
+ if ( ! node . overrides . isEqual ( this . overrides ) ) {
1034
+ return false
1035
+ }
1036
+ } else {
1037
+ if ( this . overrides ) {
1038
+ return false
1039
+ }
1040
+ }
1012
1041
}
1042
+
1013
1043
ignorePeers = new Set ( ignorePeers )
1014
1044
1015
1045
// gather up all the deps of this node and that are only depended
@@ -1077,8 +1107,13 @@ class Node {
1077
1107
return false
1078
1108
}
1079
1109
1080
- // if we prefer dedupe, or if the version is greater/equal, take the other
1081
- if ( preferDedupe || semver . gte ( other . version , this . version ) ) {
1110
+ // if we prefer dedupe, or if the version is equal, take the other
1111
+ if ( preferDedupe || semver . eq ( other . version , this . version ) ) {
1112
+ return true
1113
+ }
1114
+
1115
+ // if our current version isn't the result of an override, then prefer to take the greater version
1116
+ if ( ! this . overridden && semver . gt ( other . version , this . version ) ) {
1082
1117
return true
1083
1118
}
1084
1119
@@ -1249,10 +1284,6 @@ class Node {
1249
1284
this [ _changePath ] ( newPath )
1250
1285
}
1251
1286
1252
- if ( parent . overrides ) {
1253
- this . overrides = parent . overrides . getNodeRule ( this )
1254
- }
1255
-
1256
1287
// clobbers anything at that path, resets all appropriate references
1257
1288
this . root = parent . root
1258
1289
}
@@ -1346,9 +1377,87 @@ class Node {
1346
1377
this . edgesOut . set ( edge . name , edge )
1347
1378
}
1348
1379
1349
- addEdgeIn ( edge ) {
1380
+ recalculateOutEdgesOverrides ( ) {
1381
+ // For each edge out propogate the new overrides through.
1382
+ for ( const edge of this . edgesOut . values ( ) ) {
1383
+ edge . reload ( true )
1384
+ if ( edge . to ) {
1385
+ edge . to . updateOverridesEdgeInAdded ( edge . overrides )
1386
+ }
1387
+ }
1388
+ }
1389
+
1390
+ updateOverridesEdgeInRemoved ( otherOverrideSet ) {
1391
+ // If this edge's overrides isn't equal to this node's overrides, then removing it won't change newOverrideSet later.
1392
+ if ( ! this . overrides || ! this . overrides . isEqual ( otherOverrideSet ) ) {
1393
+ return false
1394
+ }
1395
+ let newOverrideSet
1396
+ for ( const edge of this . edgesIn ) {
1397
+ if ( newOverrideSet && edge . overrides ) {
1398
+ newOverrideSet = OverrideSet . findSpecificOverrideSet ( edge . overrides , newOverrideSet )
1399
+ } else {
1400
+ newOverrideSet = edge . overrides
1401
+ }
1402
+ }
1403
+ if ( this . overrides . isEqual ( newOverrideSet ) ) {
1404
+ return false
1405
+ }
1406
+ this . overrides = newOverrideSet
1407
+ if ( this . overrides ) {
1408
+ // Optimization: if there's any override set at all, then no non-extraneous node has an empty override set. So if we temporarily have no
1409
+ // override set (for example, we removed all the edges in), there's no use updating all the edges out right now. Let's just wait until
1410
+ // we have an actual override set later.
1411
+ this . recalculateOutEdgesOverrides ( )
1412
+ }
1413
+ return true
1414
+ }
1415
+
1416
+ // This logic isn't perfect either. When we have two edges in that have different override sets, then we have to decide which set is correct.
1417
+ // This function assumes the more specific override set is applicable, so if we have dependencies A->B->C and A->C
1418
+ // and an override set that specifies what happens for C under A->B, this will work even if the new A->C edge comes along and tries to change
1419
+ // the override set.
1420
+ // The strictly correct logic is not to allow two edges with different overrides to point to the same node, because even if this node can satisfy
1421
+ // both, one of its dependencies might need to be different depending on the edge leading to it.
1422
+ // However, this might cause a lot of duplication, because the conflict in the dependencies might never actually happen.
1423
+ updateOverridesEdgeInAdded ( otherOverrideSet ) {
1424
+ if ( ! otherOverrideSet ) {
1425
+ // Assuming there are any overrides at all, the overrides field is never undefined for any node at the end state of the tree.
1426
+ // So if the new edge's overrides is undefined it will be updated later. So we can wait with updating the node's overrides field.
1427
+ return false
1428
+ }
1429
+ if ( ! this . overrides ) {
1430
+ this . overrides = otherOverrideSet
1431
+ this . recalculateOutEdgesOverrides ( )
1432
+ return true
1433
+ }
1434
+ if ( this . overrides . isEqual ( otherOverrideSet ) ) {
1435
+ return false
1436
+ }
1437
+ const newOverrideSet = OverrideSet . findSpecificOverrideSet ( this . overrides , otherOverrideSet )
1438
+ if ( newOverrideSet ) {
1439
+ if ( ! this . overrides . isEqual ( newOverrideSet ) ) {
1440
+ this . overrides = newOverrideSet
1441
+ this . recalculateOutEdgesOverrides ( )
1442
+ return true
1443
+ }
1444
+ return false
1445
+ }
1446
+ // This is an error condition. We can only get here if the new override set is in conflict with the existing.
1447
+ log . silly ( 'Conflicting override sets' , this . name )
1448
+ }
1449
+
1450
+ deleteEdgeIn ( edge ) {
1451
+ this . edgesIn . delete ( edge )
1350
1452
if ( edge . overrides ) {
1351
- this . overrides = edge . overrides
1453
+ this . updateOverridesEdgeInRemoved ( edge . overrides )
1454
+ }
1455
+ }
1456
+
1457
+ addEdgeIn ( edge ) {
1458
+ // We need to handle the case where the new edge in has an overrides field which is different from the current value.
1459
+ if ( ! this . overrides || ! this . overrides . isEqual ( edge . overrides ) ) {
1460
+ this . updateOverridesEdgeInAdded ( edge . overrides )
1352
1461
}
1353
1462
1354
1463
this . edgesIn . add ( edge )
0 commit comments