import { Workspace } from "@yarnpkg/core" ; class Graph { nodes : NodeGraph = { } ; size = 0 ; runSize = 0 ; ran : Set < string > = new Set ( ) ; addNode ( id ) { if ( this . nodes [ id ] ) { return this . nodes [ id ] ; } const newNode = new Node ( id , this ) ; this . nodes [ id ] = newNode ; this . size ++ ; return newNode ; } getNode ( id ) { if ( this . nodes [ id ] ) { return this . nodes [ id ] ; } } resetRuns ( ) { this . ran = new Set ( ) ; } async resolve ( node ) { const resolved = new Set ( ) ; const unresolved = new Set ( ) ; await this . resolveNode ( node , resolved , unresolved ) ; } private async resolveNode ( node , resolved , unresolved ) { unresolved . add ( node . id ) ; for ( const dep of node . dependencies ) { if ( ! resolved . has ( dep . id ) ) { if ( unresolved . has ( dep . id ) ) { throw new CyclicDependencyError ( `${ node . id } has a cyclic dependency on ${ dep . id }` ) ; } await this . resolveNode ( dep , resolved , unresolved ) ; } } resolved . add ( node . id ) ; unresolved . delete ( node . id ) ; } async run ( nodes ) { const queue = new Set < RunQueueItem > ( ) ; const progress = new Set < Node > ( ) ; const runLog = { } ; for ( const n of nodes ) { this . resolveQueue ( n , queue , runLog ) ; } await new Promise < void > ( ( resolve ) => { this . workLoop ( queue , runLog , progress , resolve ) ; } ) ; return runLog ; } private workLoop ( queue , runLog , progress , resolve ) { if ( queue . size !== 0 ) { queue . forEach ( ( q ) => { if ( q . canStart ( runLog ) ) { if ( q ?. node ?. runCallback ) { q ?. node ?. runCallback ( runLog ) ; progress . add ( q . node ) ; } else { runLog [ q . node . id ] = { success : true , done : true } ; } queue . delete ( q ) ; } } ) ; } progress . forEach ( ( n , i ) => { if ( runLog [ n . id ] . done ) { progress . delete ( i ) ; } } ) ; if ( Object . keys ( runLog ) . map ( ( id ) => runLog [ id ] ?. done ?? true ) . every ( ( v ) => v === true ) ) { resolve ( ) ; return ; } setTimeout ( ( ) => this . workLoop ( queue , runLog , progress , resolve ) , 30 ) ; } private resolveQueue ( node , queue , runLog ) { const parentDependencies = [ ] ; for ( const dep of node . dependencies ) { parentDependencies . push ( dep . id ) ; if ( ! runLog [ dep . id ] && dep . runCallback ) { runLog [ dep . id ] = { ... Graph . RunLogInit } ; const childDependencies = this . resolveQueue ( dep , queue , runLog ) ; const queueItem = { node : dep , canStart : Graph . QueueItemCanStart ( childDependencies ) , } ; queue . add ( queueItem ) ; } } if ( ! runLog [ node . id ] && node . runCallback ) { runLog [ node . id ] = { ... Graph . RunLogInit } ; const queueItem = { node , canStart : Graph . QueueItemCanStart ( parentDependencies ) , } ; queue . add ( queueItem ) ; } return parentDependencies ; } private static RunLogInit = { success : false , done : false } ; private static QueueItemCanStart = ( dependencies ) => ( runLog ) : boolean => { return dependencies . map ( ( id ) => runLog [ id ] ?. done ?? true ) . every ( ( v ) => v === true ) ; } ; } type RunQueueItem = { node : Node ; canStart : ( runLog : RunLog ) => boolean ; } ; class Node { id : string ; dependencies : Node [ ] ; graph : Graph ; workspace ? : Workspace ; runCallback ? : RunLogCallback ; constructor ( id , graph ) { this . id = id ; this . dependencies = [ ] ; this . graph = graph ; } addDependency ( node ) { this . dependencies . push ( node ) ; return this ; } addWorkSpace ( workspace ) { this . workspace = workspace ; return this ; } addRunCallback ( callback ) { if ( this . runCallback ) { return this ; } this . runCallback = ( runLog ) => { return callback ( ) . then ( ( success ) => { runLog [ this . id ] = { done : true , success } ; } ) ; } ; this . graph . runSize ++ ; return this ; } } class CyclicDependencyError extends Error { code : string ; constructor ( message ) { super ( message ) ; this . name = "CyclicDependencyError" ; this . code = "YN0003" ; } } type RunLog = { [ id ] : { success : boolean ; done : boolean ; } ; } ; type RunSuccess = boolean ; type RunCallback = ( ) => Promise < RunSuccess > ; type RunLogCallback = ( runLog : RunLog ) => void ; type NodeGraph = { [ id ] : Node ; } ; export { Graph , Node , CyclicDependencyError , RunCallback } ;