Skip to content

Add Option to Preserve Symlinks in directory() Instead of Following Them #808

@danieldanieltata

Description

@danieldanieltata

Currently, when using the directory() method, Archiver follows symlinks by default. This means that if a symlink is present within a directory, it archives the target file (using its relative path) rather than preserving the symlink itself. This behavior can be problematic when the desired outcome is to retain symlink information in the archive.

Steps to Reproduce:
1. Create a directory containing a symlink (e.g., a symlink pointing to another file or directory).
2. Use archiver.directory('your_directory', 'dest_directory') to archive the folder.
3. Notice that the symlink is resolved to its target, and its relative path is used in the archive.

Expected Behavior:
There should be an option to disable following symlinks so that symlinks are archived as symlinks. For example, an option like
{ followSymlinks: false } could be added to the directory() method. This would allow the symlink to be preserved, similar to how archive.symlink() works.

Actual Behavior:
The current behavior always follows symlinks, meaning the archive contains the files/directories that the symlinks point to rather than the symlink entries themselves.

Impact:
This limitation forces users to implement custom logic to handle symlinks when preservation is required, which increases code complexity and can negatively impact performance, especially with large directory structures.

For example, here’s a snippet of the custom implementation I had to create:

const fs = require('fs');
const path = require('path');
const archiver = require('archiver');

const output = fs.createWriteStream('output.zip');
const archive = archiver('zip', { zlib: { level: 9 } });

archive.pipe(output);

function addDirectory(dirPath, archivePath) {
  fs.readdirSync(dirPath).forEach(item => {
    const fullPath = path.join(dirPath, item);
    const stats = fs.lstatSync(fullPath);

    if (stats.isSymbolicLink()) {
      // Read the symlink target. This will likely be a relative path.
      const target = fs.readlinkSync(fullPath);
      // Add the symlink entry to the archive.
      archive.symlink(path.join(archivePath, item), target);
    } else if (stats.isDirectory()) {
      // Recursively add directory contents.
      addDirectory(fullPath, path.join(archivePath, item));
    } else {
      // Regular file.
      archive.file(fullPath, { name: path.join(archivePath, item) });
    }
  });
}

// Replace 'your_directory' with the directory you want to archive.
addDirectory('your_directory', 'your_directory');

// Finalize the archive.
archive.finalize();

This workaround clearly shows the extra effort required to preserve symlinks as opposed to the expected behavior of a built-in option.

Suggestion:
Introduce an optional parameter in the directory() method, such as:

archive.directory('your_directory', 'dest_directory', { followSymlinks: false });

When this option is set to false, Archiver would add symlink entries (using archive.symlink()) instead of following them.

Conclusion:
Providing an option to preserve symlinks would significantly improve Archiver’s usability for projects where maintaining symlink metadata is critical.

Please let me know if I’m missing something or if further details are needed.
Im working on a PR, but let me know if this make sense

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions