MCPcopy
hub / github.com/codeaashu/claude-code / copyDir

Function copyDir

src/utils/plugins/pluginLoader.ts:293–348  ·  view source on GitHub ↗
(src: string, dest: string)

Source from the content-addressed store, hash-verified

291 * Exported for testing purposes.
292 */
293export async function copyDir(src: string, dest: string): Promise<void> {
294 await getFsImplementation().mkdir(dest)
295
296 const entries = await readdir(src, { withFileTypes: true })
297
298 for (const entry of entries) {
299 const srcPath = join(src, entry.name)
300 const destPath = join(dest, entry.name)
301
302 if (entry.isDirectory()) {
303 await copyDir(srcPath, destPath)
304 } else if (entry.isFile()) {
305 await copyFile(srcPath, destPath)
306 } else if (entry.isSymbolicLink()) {
307 const linkTarget = await readlink(srcPath)
308
309 // Resolve the symlink to get the actual target path
310 // This prevents circular symlinks when src and dest overlap (e.g., via symlink chains)
311 let resolvedTarget: string
312 try {
313 resolvedTarget = await realpath(srcPath)
314 } catch {
315 // Broken symlink - copy the raw link target as-is
316 await symlink(linkTarget, destPath)
317 continue
318 }
319
320 // Resolve the source directory to handle symlinked source dirs
321 let resolvedSrc: string
322 try {
323 resolvedSrc = await realpath(src)
324 } catch {
325 resolvedSrc = src
326 }
327
328 // Check if target is within the source tree (using proper path prefix matching)
329 const srcPrefix = resolvedSrc.endsWith(sep)
330 ? resolvedSrc
331 : resolvedSrc + sep
332 if (
333 resolvedTarget.startsWith(srcPrefix) ||
334 resolvedTarget === resolvedSrc
335 ) {
336 // Target is within source tree - create relative symlink that preserves
337 // the same structure in the destination
338 const targetRelativeToSrc = relative(resolvedSrc, resolvedTarget)
339 const destTargetPath = join(dest, targetRelativeToSrc)
340 const relativeLinkPath = relative(dirname(destPath), destTargetPath)
341 await symlink(relativeLinkPath, destPath)
342 } else {
343 // Target is outside source tree - use absolute resolved path
344 await symlink(resolvedTarget, destPath)
345 }
346 }
347 }
348}
349
350/**

Callers 3

installFromNpmFunction · 0.85
installFromLocalFunction · 0.85

Calls 2

getFsImplementationFunction · 0.85
readdirFunction · 0.85

Tested by

no test coverage detected