@@ -35,7 +35,14 @@ description: Deploys the application
3535
3636Deploy instructions.`
3737
38- mockFs . stat = vi . fn ( ) . mockResolvedValue ( { isDirectory : ( ) => true } )
38+ mockFs . stat = vi
39+ . fn ( )
40+ // First call for directory check
41+ . mockResolvedValueOnce ( { isDirectory : ( ) => true } as any )
42+ // Subsequent calls for symlink target checks
43+ . mockResolvedValueOnce ( { isFile : ( ) => true , isDirectory : ( ) => false } as any ) // setup.md symlink points to file
44+ . mockResolvedValueOnce ( { isFile : ( ) => true , isDirectory : ( ) => false } as any ) // build.md symlink points to file
45+
3946 mockFs . readdir = vi . fn ( ) . mockResolvedValue ( [
4047 { name : "setup.md" , isFile : ( ) => false , isSymbolicLink : ( ) => true } , // Symbolic link
4148 { name : "deploy.md" , isFile : ( ) => true , isSymbolicLink : ( ) => false } , // Regular file
@@ -86,10 +93,17 @@ description: Linked command
8693
8794This command is accessed via symbolic link.`
8895
89- mockFs . stat = vi . fn ( ) . mockResolvedValue ( { isDirectory : ( ) => true } )
96+ // Mock directory stat
97+ mockFs . stat = vi
98+ . fn ( )
99+ // First call for directory check
100+ . mockResolvedValueOnce ( { isDirectory : ( ) => true } as any )
101+ // Second call for symlink target check (linked.md points to a file)
102+ . mockResolvedValueOnce ( { isFile : ( ) => true , isDirectory : ( ) => false } as any )
103+
90104 mockFs . readdir = vi . fn ( ) . mockResolvedValue ( [
91105 { name : "test.md" , isFile : ( ) => true , isSymbolicLink : ( ) => false } , // Regular file
92- { name : "linked.md" , isFile : ( ) => false , isSymbolicLink : ( ) => true } , // Symbolic link
106+ { name : "linked.md" , isFile : ( ) => false , isSymbolicLink : ( ) => true } , // Symbolic link to file
93107 { name : "not-markdown.txt" , isFile : ( ) => true , isSymbolicLink : ( ) => false } , // Should be ignored
94108 { name : "symlink.txt" , isFile : ( ) => false , isSymbolicLink : ( ) => true } , // Non-markdown symlink, should be ignored
95109 ] )
@@ -118,15 +132,16 @@ This command is accessed via symbolic link.`
118132
119133This is a valid command.`
120134
121- mockFs . stat = vi . fn ( ) . mockResolvedValue ( { isDirectory : ( ) => true } )
135+ mockFs . stat = vi
136+ . fn ( )
137+ . mockResolvedValueOnce ( { isDirectory : ( ) => true } as any ) // Directory check
138+ . mockRejectedValueOnce ( new Error ( "ENOENT: no such file or directory" ) ) // Broken symlink
139+
122140 mockFs . readdir = vi . fn ( ) . mockResolvedValue ( [
123141 { name : "valid.md" , isFile : ( ) => true , isSymbolicLink : ( ) => false } , // Regular file
124142 { name : "broken-link.md" , isFile : ( ) => false , isSymbolicLink : ( ) => true } , // Broken symbolic link
125143 ] )
126- mockFs . readFile = vi
127- . fn ( )
128- . mockResolvedValueOnce ( validContent )
129- . mockRejectedValueOnce ( new Error ( "ENOENT: no such file or directory" ) ) // Broken link
144+ mockFs . readFile = vi . fn ( ) . mockResolvedValueOnce ( validContent )
130145
131146 const result = await getCommands ( "/test/cwd" )
132147
@@ -139,6 +154,35 @@ This is a valid command.`
139154 )
140155 } )
141156
157+ it ( "should ignore symbolic links pointing to directories" , async ( ) => {
158+ const validContent = `# Valid Command
159+
160+ This is a valid command.`
161+
162+ mockFs . stat = vi
163+ . fn ( )
164+ . mockResolvedValueOnce ( { isDirectory : ( ) => true } as any ) // Commands directory check
165+ . mockResolvedValueOnce ( { isFile : ( ) => false , isDirectory : ( ) => true } as any ) // dir-link.md points to a directory
166+
167+ mockFs . readdir = vi . fn ( ) . mockResolvedValue ( [
168+ { name : "valid.md" , isFile : ( ) => true , isSymbolicLink : ( ) => false } , // Regular file
169+ { name : "dir-link.md" , isFile : ( ) => false , isSymbolicLink : ( ) => true } , // Symbolic link to directory
170+ ] )
171+ mockFs . readFile = vi . fn ( ) . mockResolvedValueOnce ( validContent )
172+
173+ const result = await getCommands ( "/test/cwd" )
174+
175+ // Should only load the valid command, ignoring the symlink to directory
176+ expect ( result ) . toHaveLength ( 1 )
177+ expect ( result [ 0 ] ) . toEqual (
178+ expect . objectContaining ( {
179+ name : "valid" ,
180+ } ) ,
181+ )
182+ // readFile should only be called once (for the valid file)
183+ expect ( mockFs . readFile ) . toHaveBeenCalledTimes ( 1 )
184+ } )
185+
142186 it ( "should prioritize project symbolic links over global commands" , async ( ) => {
143187 const projectSymlinkContent = `---
144188description: Project symbolic link command
@@ -157,7 +201,11 @@ description: Global command
157201Global command content.`
158202
159203 // Mock both directories
160- mockFs . stat = vi . fn ( ) . mockResolvedValue ( { isDirectory : ( ) => true } )
204+ mockFs . stat = vi
205+ . fn ( )
206+ . mockResolvedValueOnce ( { isDirectory : ( ) => true } as any ) // Global directory
207+ . mockResolvedValueOnce ( { isDirectory : ( ) => true } as any ) // Project directory
208+ . mockResolvedValueOnce ( { isFile : ( ) => true , isDirectory : ( ) => false } as any ) // Project symlink target
161209
162210 // First call for global directory scan, second for project directory scan
163211 mockFs . readdir = vi
0 commit comments