node-archiver icon indicating copy to clipboard operation
node-archiver copied to clipboard

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

Open danieldanieltata opened this issue 9 months ago • 2 comments

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

danieldanieltata avatar Mar 04 '25 07:03 danieldanieltata

@danieldanieltata, I'm curious to know which version of Archiver you are using, of if you are passing any other option. When we use archiver.directory('your_directory', 'dest_directory'), the symlink file (the pointer) is included, but Archiver does not follow the symlink to add its content.
In fact, we would like to include the symlink content, and @epeicher created a PR suggesting this behavior: https://github.com/archiverjs/node-archiver/pull/810
I tried it on archiver versions 6.0.1 and 7.0.1.

sejas avatar May 28 '25 13:05 sejas

Hi @sejas, I’m using version 7.0.1. When I try to archive a directory that contains a symlink pointing to a non-existent destination in the replica, I get an error. I’m not using any additional options in the directory function.

danieldanieltata avatar May 29 '25 11:05 danieldanieltata