@@ -185,12 +185,14 @@ const ModLocal = ({
185185 size,
186186 duplicateCount,
187187 duplicateFiles,
188- } : ModInfo & { optional ?: boolean } ) => {
188+ renderPath = [ ] ,
189+ } : ModInfo & { optional ?: boolean ; renderPath ?: string [ ] } ) => {
189190 const { download } = useGlobalContext ( ) ;
190191 const [ expanded , setExpanded ] = useState ( false ) ;
191192 const [ hovered , setHovered ] = useState ( false ) ;
192193
193194 const ctx = useContext ( modListContext ) ;
195+ const hasCycle = renderPath . includes ( name ) ;
194196
195197 const hasDeps = useMemo (
196198 ( ) => dependencies . some ( ( v ) => ! excludeList . includes ( v . name ) ) ,
@@ -288,6 +290,12 @@ const ModLocal = ({
288290 </ ModBadge >
289291 ) : null ) }
290292
293+ { hasCycle && (
294+ < ModBadge bg = "#9c27b0" color = "white" >
295+ { _i18n . t ( '循环依赖' ) }
296+ </ ModBadge >
297+ ) }
298+
291299 { optional && (
292300 < ModBadge bg = "#ff9800" color = "white" >
293301 { _i18n . t ( '可选依赖' ) }
@@ -389,18 +397,18 @@ const ModLocal = ({
389397 < Icon name = "delete" />
390398 </ span >
391399 ) }
392- { ( ! optional || ctx ?. fullTree ) && expanded && (
400+ { ( ! optional || ctx ?. fullTree ) && expanded && ! hasCycle && (
393401 < div className = { `childTree ${ expanded && 'expanded' } ` } >
394402 { dependencies . map ( ( v ) => (
395- < Mod { ...v } />
403+ < Mod { ...v } renderPath = { [ ... renderPath , name ] } />
396404 ) ) }
397405 </ div >
398406 ) }
399407 </ div >
400408 ) ;
401409} ;
402410
403- const Mod = ( props : ModDepInfo ) => {
411+ const Mod = ( props : ModDepInfo & { renderPath ?: string [ ] } ) => {
404412 if ( excludeList . includes ( props . name ) ) {
405413 return null ;
406414 }
@@ -574,39 +582,52 @@ export const Manage = () => {
574582 size : mod . size ,
575583 _deps : mod . deps ,
576584 resolveDependencies : ( ) => {
577- let status = 'resolved' ;
578- let message = '' ;
579-
580- const mergeSM = (
581- s : {
582- status : DepState ;
583- message : string ;
584- } ,
585- name : String
585+ const resolveModDependencies = (
586+ current : BackendModInfo ,
587+ visiting = new Set < string > ( )
586588 ) => {
587- if ( s . status === 'resolved' ) return ;
588- if ( status === 'resolved' ) {
589- status = s . status ;
589+ if ( visiting . has ( current . name ) ) {
590+ return { status : 'resolved' , message : '' } as DepResolveResult ;
590591 }
591- message += ` | ${ name } (${ s . status } ):${ s . message } ` ;
592- } ;
593592
594- for ( const dep of mod . deps ) {
595- if (
596- excludeList . includes ( dep . name ) ||
597- ( dep . optional && ! checkOptionalDep )
598- )
599- continue ;
593+ visiting . add ( current . name ) ;
594+
595+ let status = 'resolved' ;
596+ let message = '' ;
597+
598+ const mergeSM = (
599+ s : {
600+ status : DepState ;
601+ message : string ;
602+ } ,
603+ name : string
604+ ) => {
605+ if ( s . status === 'resolved' ) return ;
606+ if ( status === 'resolved' ) {
607+ status = s . status ;
608+ }
609+ message += ` | ${ name } (${ s . status } ):${ s . message } ` ;
610+ } ;
611+
612+ for ( const dep of current . deps ) {
613+ if (
614+ excludeList . includes ( dep . name ) ||
615+ ( dep . optional && ! checkOptionalDep )
616+ ) {
617+ continue ;
618+ }
619+
620+ if ( ! modMap . has ( dep . name ) ) {
621+ mergeSM ( { status : 'missing' , message : '' } , dep . name ) ;
622+ continue ;
623+ }
600624
601- if ( ! modMap . has ( dep . name ) ) {
602- mergeSM ( { status : 'missing' , message : '' } , dep . name ) ;
603- } else {
604625 const installedDep = modMap . get ( dep . name ) ! ;
605626 if ( compareVersion ( installedDep . version , dep . version ) < 0 ) {
606627 mergeSM (
607628 {
608629 status : 'mismatched-version' ,
609- message : `${ mod . name } requires ${ installedDep . name } >= ${ dep . version } but got ${ installedDep . version } ` ,
630+ message : `${ current . name } requires ${ installedDep . name } >= ${ dep . version } but got ${ installedDep . version } ` ,
610631 } ,
611632 dep . name
612633 ) ;
@@ -616,18 +637,25 @@ export const Manage = () => {
616637 mergeSM (
617638 {
618639 status : 'not-enabled' ,
619- message : `${ mod . name } requires ${ installedDep . name } to be enabled` ,
640+ message : `${ current . name } requires ${ installedDep . name } to be enabled` ,
620641 } ,
621642 dep . name
622643 ) ;
623644 }
624645
625- const depRes = installedDep . resolveDependencies ( ) ;
626- mergeSM ( depRes , dep . name ) ;
646+ const depBackend = installedMods . find ( ( v ) => v . name === dep . name ) ;
647+ if ( depBackend ) {
648+ const depRes = resolveModDependencies ( depBackend , visiting ) ;
649+ mergeSM ( depRes , dep . name ) ;
650+ }
627651 }
628- }
629652
630- return { status, message } as DepResolveResult ;
653+ visiting . delete ( current . name ) ;
654+
655+ return { status, message } as DepResolveResult ;
656+ } ;
657+
658+ return resolveModDependencies ( mod ) ;
631659 } ,
632660 duplicateCount : 1 ,
633661 duplicateFiles : [ mod . file ] ,
@@ -900,11 +928,18 @@ export const Manage = () => {
900928 'Miao.CelesteNet.Client' ,
901929 ] ;
902930
931+ const visited = new Set < string > ( ) ;
932+
903933 const addToSwitchList = ( name : string ) => {
934+ if ( visited . has ( name ) ) return ;
935+ visited . add ( name ) ;
936+
904937 const mod = installedModMap . get ( name ) ;
905938 if ( mod ) {
906939 mod . enabled = enabled ;
907940 switchList . push ( name ) ;
941+ } else {
942+ return ;
908943 }
909944
910945 if ( recursive ) {
@@ -975,7 +1010,12 @@ export const Manage = () => {
9751010
9761011 // Find orphaned mods (mods that will have no references after deletion)
9771012 const orphanedMods : ModInfo [ ] = [ ] ;
1013+ const visited = new Set < string > ( ) ;
1014+
9781015 const checkOrphans = ( mod : ModInfo ) => {
1016+ if ( visited . has ( mod . name ) ) return ;
1017+ visited . add ( mod . name ) ;
1018+
9791019 for ( const dep of mod . dependencies ) {
9801020 if ( '_missing' in dep ) continue ;
9811021 const depInfo = installedModMap . get ( dep . name ) ;
0 commit comments