11import events from 'node:events'
2- import { existsSync , readFileSync , realpathSync } from 'node:fs'
2+ import { readFileSync , realpathSync } from 'node:fs'
33import https from 'node:https'
44import path from 'node:path'
55import rl from 'node:readline'
@@ -35,14 +35,16 @@ type ArboristClass = typeof BaseArborist & {
3535 new ( ...args : any ) : typeof BaseArborist
3636}
3737
38- type EdgeClass = BaseEdge & {
38+ type EdgeClass = Omit < BaseEdge , 'overrides' | 'reload' > & {
3939 optional : boolean
4040 overrides : OverrideSet | undefined
4141 peer : boolean
4242 peerConflicted : boolean
4343 rawSpec : string
4444 get spec ( ) : string
45+ get to ( ) : NodeClass | null
4546 new ( ...args : any ) : EdgeClass
47+ reload ( hard ?: boolean ) : void
4648 satisfiedBy ( node : NodeClass ) : boolean
4749}
4850
@@ -78,17 +80,20 @@ type InstallEffect = {
7880 newPackage : PURLParts
7981}
8082
81- type NodeClass = BaseNode & {
83+ type NodeClass = Omit < BaseNode , 'edgesOut' | 'isTop' | 'parent' | 'resolve' > & {
8284 name : string
8385 version : string
8486 edgesIn : Set < SafeEdge >
8587 edgesOut : Map < string , SafeEdge >
8688 hasShrinkwrap : boolean
8789 inShrinkwrap : boolean | undefined
90+ isTop : boolean | undefined
8891 overrides : OverrideSet | undefined
89- new ( ...args : any ) : BaseNode
92+ parent : NodeClass | null
93+ new ( ...args : any ) : NodeClass
9094 addEdgeIn ( edge : SafeEdge ) : void
9195 addEdgeOut ( edge : SafeEdge ) : void
96+ resolve ( name : string ) : NodeClass
9297}
9398
9499interface OverrideSet {
@@ -220,7 +225,7 @@ async function* batchScan(
220225 }
221226 } )
222227 }
223- // TODO: migrate to SDK
228+ // TODO: Migrate to SDK.
224229 const pkgDataReq = https
225230 . request ( `${ API_V0_URL } /scan/batch` , {
226231 method : 'POST' ,
@@ -243,8 +248,9 @@ async function* batchScan(
243248
244249function deleteEdgeIn ( node : NodeClass , edge : SafeEdge ) {
245250 node . edgesIn . delete ( edge )
246- if ( edge . overrides ) {
247- updateNodeOverrideSetDueToEdgeRemoval ( node , edge . overrides )
251+ const { overrides } = edge
252+ if ( overrides ) {
253+ updateNodeOverrideSetDueToEdgeRemoval ( node , overrides )
248254 }
249255}
250256
@@ -293,35 +299,43 @@ function findSpecificOverrideSet(
293299 overrideSet = overrideSet . parent
294300 }
295301 console . error ( 'Conflicting override sets' )
302+ return undefined
296303}
297304
298305function maybeReadfileSync ( filepath : string ) : string | undefined {
299306 try {
300- return existsSync ( filepath ) ? readFileSync ( filepath , 'utf8' ) : undefined
307+ return readFileSync ( filepath , 'utf8' )
301308 } catch { }
302309 return undefined
303310}
304311
305312function overrideSetsChildrenAreEqual (
306313 overrideSet : OverrideSet ,
307314 other : OverrideSet
308- ) {
309- const { children } = overrideSet
310- const { children : otherChildren } = other
311- if ( children . size !== otherChildren . size ) {
312- return false
313- }
314- for ( const key of children . keys ( ) ) {
315- if ( ! otherChildren . has ( key ) ) {
316- return false
315+ ) : boolean {
316+ const queue : [ OverrideSet , OverrideSet ] [ ] = [ [ overrideSet , other ] ]
317+ let pos = 0
318+ let { length : queueLength } = queue
319+ while ( pos < queueLength ) {
320+ if ( pos === LOOP_SENTINEL ) {
321+ throw new Error ( 'Detected infinite loop while comparing override sets' )
317322 }
318- const child = < OverrideSet > children . get ( key )
319- const otherChild = < OverrideSet > otherChildren . get ( key )
320- if ( child ! . value !== otherChild ! . value ) {
323+ const { 0 : currSet , 1 : currOtherSet } = queue [ pos ++ ] !
324+ const { children } = currSet
325+ const { children : otherChildren } = currOtherSet
326+ if ( children . size !== otherChildren . size ) {
321327 return false
322328 }
323- if ( ! overrideSetsChildrenAreEqual ( child , otherChild ) ) {
324- return false
329+ for ( const key of children . keys ( ) ) {
330+ if ( ! otherChildren . has ( key ) ) {
331+ return false
332+ }
333+ const child = < OverrideSet > children . get ( key )
334+ const otherChild = < OverrideSet > otherChildren . get ( key )
335+ if ( child ! . value !== otherChild ! . value ) {
336+ return false
337+ }
338+ queue [ queueLength ++ ] = [ child , otherChild ]
325339 }
326340 }
327341 return true
@@ -479,7 +493,7 @@ function pkgidParts(pkgid: string) {
479493
480494function recalculateOutEdgesOverrides ( node : NodeClass ) {
481495 // For each edge out propagate the new overrides through.
482- for ( const [ , edge ] of node . edgesOut ) {
496+ for ( const edge of node . edgesOut . values ( ) ) {
483497 edge . reload ( true )
484498 if ( edge . to ) {
485499 updateNodeOverrideSet ( edge . to , edge . overrides )
@@ -505,25 +519,27 @@ function updateNodeOverrideSetDueToEdgeRemoval(
505519 node : NodeClass ,
506520 other : OverrideSet
507521) {
522+ const { overrides } = node
508523 // If this edge's overrides isn't equal to this node's overrides, then removing
509524 // it won't change newOverrideSet later.
510- if ( ! node . overrides || ! overrideSetsEqual ( node . overrides , other ) ) {
525+ if ( ! overrides || ! overrideSetsEqual ( overrides , other ) ) {
511526 return false
512527 }
513528 let newOverrideSet
514529 for ( const edge of node . edgesIn ) {
530+ const { overrides : edgeOverrides } = edge
515531 if ( newOverrideSet ) {
516- newOverrideSet = findSpecificOverrideSet ( edge . overrides , newOverrideSet )
532+ newOverrideSet = findSpecificOverrideSet ( edgeOverrides , newOverrideSet )
517533 } else {
518- newOverrideSet = edge . overrides
534+ newOverrideSet = edgeOverrides
519535 }
520536 }
521- if ( overrideSetsEqual ( node . overrides , newOverrideSet ) ) {
537+ if ( overrideSetsEqual ( overrides , newOverrideSet ) ) {
522538 return false
523539 }
524540 node . overrides = newOverrideSet
525- if ( node . overrides ) {
526- // Optimization: if there's any override set at all, then no non-extraneous
541+ if ( newOverrideSet ) {
542+ // Optimization: If there's any override set at all, then no non-extraneous
527543 // node has an empty override set. So if we temporarily have no override set
528544 // (for example, we removed all the edges in), there's no use updating all
529545 // the edges out right now. Let's just wait until we have an actual override
@@ -565,15 +581,17 @@ function updateNodeOverrideSet(
565581 }
566582 const newOverrideSet = findSpecificOverrideSet ( overrides , otherOverrideSet )
567583 if ( newOverrideSet ) {
568- if ( ! overrideSetsEqual ( overrides , newOverrideSet ) ) {
569- node . overrides = newOverrideSet
570- recalculateOutEdgesOverrides ( node )
571- return true
584+ if ( overrideSetsEqual ( overrides , newOverrideSet ) ) {
585+ return false
572586 }
573- return false
587+ node . overrides = newOverrideSet
588+ recalculateOutEdgesOverrides ( node )
589+ return true
574590 }
575591 // This is an error condition. We can only get here if the new override set is
576592 // in conflict with the existing.
593+ console . error ( 'Conflicting override sets' )
594+ return false
577595}
578596
579597function walk (
@@ -635,8 +653,14 @@ function walk(
635653 return needInfoOn
636654}
637655
638- // An edge in the dependency graph
639- // Represents a dependency relationship of some kind
656+ // Copied from
657+ // https://github.com/npm/cli/blob/v10.9.0/workspaces/arborist/lib/edge.js:
658+ // The npm application
659+ // Copyright (c) npm, Inc. and Contributors
660+ // Licensed on the terms of The Artistic License 2.0
661+ //
662+ // An edge in the dependency graph.
663+ // Represents a dependency relationship of some kind.
640664
641665class SafeEdge extends Edge {
642666 #safeAccept: string | undefined
@@ -648,7 +672,6 @@ class SafeEdge extends Edge {
648672 constructor ( options : EdgeOptions ) {
649673 const { accept, from } = options
650674 // Defer to supper to validate options and assign non-private values.
651- // @ts -ignore: Incorrectly typed.
652675 super ( options )
653676 if ( accept !== undefined ) {
654677 this . #safeAccept = accept || '*'
@@ -660,7 +683,7 @@ class SafeEdge extends Edge {
660683 this . reload ( true )
661684 }
662685
663- // return the edge data, and an explanation of how that edge came to be here
686+ // Return the edge data, and an explanation of how that edge came to be here.
664687 // @ts -ignore: Edge#explain is defined with an unused `seen = []` param.
665688 override explain ( ) {
666689 if ( ! this . #safeExplanation) {
@@ -733,7 +756,6 @@ class SafeEdge extends Edge {
733756 return this . #safeAccept
734757 }
735758
736- // @ts -ignore: Incorrectly typed as a property instead of an accessor.
737759 override get error ( ) {
738760 if ( ! this . #safeError) {
739761 if ( ! this . #safeTo) {
@@ -770,6 +792,8 @@ class SafeEdge extends Edge {
770792 const newTo = this . #safeFrom?. resolve ( this . name )
771793 if ( newTo !== this . #safeTo) {
772794 if ( this . #safeTo) {
795+ // Instead of `this.#safeTo.edgesIn.delete(this)` we patch based on
796+ // https://github.com/npm/cli/pull/7025.
773797 deleteEdgeIn ( this . #safeTo, this )
774798 }
775799 this . #safeTo = < NodeClass > newTo ?? null
@@ -785,6 +809,8 @@ class SafeEdge extends Edge {
785809 detach ( ) {
786810 this . #safeExplanation = null
787811 if ( this . #safeTo) {
812+ // Instead of `this.#safeTo.edgesIn.delete(this)` we patch based on
813+ // https://github.com/npm/cli/pull/7025.
788814 deleteEdgeIn ( this . #safeTo, this )
789815 }
790816 if ( this . #safeFrom) {
@@ -830,11 +856,12 @@ export class SafeArborist extends Arborist {
830856 ) : Promise < NodeClass > {
831857 // SafeArborist has suffered side effects and must be rebuilt from scratch.
832858 const arb = new Arborist ( ...( this as any ) [ kCtorArgs ] )
833- const ret = < NodeClass > await arb . reify ( ...args )
859+ const ret = < unknown > await arb . reify ( ...args )
834860 Object . assign ( this , arb )
835- return ret
861+ return < NodeClass > ret
836862 }
837863
864+ // @ts -ignore Incorrectly typed.
838865 override async reify (
839866 ...args : Parameters < InstanceType < ArboristClass > [ 'reify' ] >
840867 ) : Promise < NodeClass > {
0 commit comments