fix: keep relative symlinks inside the copied fixture - #12
Merged
Merged
Conversation
`fs.cp` resolves relative symlink targets to absolute paths unless `verbatimSymlinks` is set. A link inside the source directory therefore pointed back at the source after the copy, so writing through it from a test modified the original tree and `cleanup()` could not undo it. Claude-Session: https://claude.ai/code/session_01PNx3qJzFSFUwKNNTVtPFE7
Keep verbatimSymlinks in the copy; the README bullets and the inline comment restated the test and the changeset.
ho991217
approved these changes
Sep 14, 2026
ho991217
left a comment
Collaborator
There was a problem hiding this comment.
Thank you for your contribution!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
README.mdpromises that every fixture lives in its own fresh temporary directory, "so tests never step on each other".Fixture.fromDirectorybreaks that promise when the source directory contains a relative symlink.fs.cprewrites relative symlink targets to absolute paths unlessverbatimSymlinksis set, so the link inside the copy resolves back into the source directory. A test that writes through it edits the original file, andcleanup()cannot undo that:fs.rmunlinks a symlink without following it, so the fixture disappears and the edit to the repository stays. Any source directory carrying a relative symlink hits this, whether or not the link was put there on purpose.The Node docs describe
verbatimSymlinksas copying "symlinks as symlinks instead of as the reference of the symlink", which does not cover the rewrite, so the script below shows it. It runs on Node 18 or later:Output on Node 24.14.1, macOS:
The first block is the behavior on
main: the copy points at the source, the test edits the repository, and cleanup leaves the edit behind.Choosing
verbatimSymlinksoverdereferencedereference: truefixes more of the problem, since it materializes every link as a regular file and absolute links stop escaping too. It costs more than this bug is worth. It fails outright on a dangling symlink, which the other two settings copy without complaint:It also copies the whole target when a link points at a large directory, and it discards the symlink, which breaks any fixture that exists to test symlink handling.
verbatimSymlinks: truekeeps the shape of the source tree and closes the path that silently edits the repository.Absolute symlinks in the source still point outside the fixture after this change. That matches what you get by reading the source directory directly, so I left it as is. Happy to reject or dereference those as well if you would rather close that too.
verbatimSymlinkslanded in Node 17.6.0, below the>=18floor inengines.Test
src/fixture.spec.tsgains one case,should keep a relative symlink pointing inside the copy. It builds a source directory at runtime, copies it throughfromDirectory, writes through the link, then asserts that the write landed infixture.rootand that the source file is untouched.To see it fail, drop
verbatimSymlinks: truefrom thefs.cpcall insrc/fixture.tsand run it again:The case is skipped on Windows, where creating a symlink needs an elevated process. The option handling in
fs.cpis the same on every platform, so POSIX coverage catches a regression.I ran
yarn lint,yarn build && yarn typecheck, andyarn vitest run(50 passing) on macOS with Node 24.14.1. The Windows, Node 20, and Node 22 legs of the CI matrix have not run yet.Checklist
README.mdandREADME.ko.mdare updated, if user-facing behavior changed.yarn changeset), if the published package changed.