fix: handle symlinks when zipping the app for export
Closes #7851
_zip resolved every walked directory and then took paths relative to the root, so a directory symlink pointing outside the app raised ValueError: ... is not in the subpath, a dangling symlink raised FileNotFoundError (both leaving a partial backend.zip), and a directory symlink inside the app archived its files twice.
- Archive names come from the unresolved walk path, so files below a symlink to a directory outside the app are stored under the link's own path (the first option in the issue). Note that
reflex export/reflex deploynow follows such a link instead of crashing, so its target's (non-hidden) contents are uploaded. - Symlinks to directories inside the app are skipped, so their files are archived once under their real path, which keeps package imports working in the deployed backend whatever order
os.walkreturns entries in. A link to a hidden in-app directory (e.g.shared -> .shared) is therefore skipped too, since hidden directories are already pruned. - Dangling symlinks and symlinks that resolve to themselves are skipped with
Skipping dangling symlink <path relative to the app>/Skipping looping symlink <path>; otherOSErrors still propagate. - Directories reached twice (e.g. a symlink loop) are walked once, using their device and inode.
- Each file is stat'ed once; the existing exclusion check reuses that result.
Tests (tests/units/utils/test_build.py): an outside directory link, a dangling link, an inside link (sorted(names) == ["a.py", "real/b.py"]), an inside link that sorts before a nested target (sub -> pkg/sub keeps pkg/sub/c.py), an outside directory that links back to itself (only the inode check stops it) and a symlink to itself; 6 fail on main. pytest tests/units/utils/test_build.py tests/units/utils/test_export.py: 41 passed; ruff check/format clean. A bugfix news fragment is included.
All Submissions:
- Have you followed the guidelines stated in CONTRIBUTING.md file?
- Have you checked to ensure there aren't any other open Pull Requests for the desired changed?
Type of change
- Bug fix (non-breaking change which fixes an issue)