node.jsexpressnode-archiver

Can't unzip file generated by node-archiver on OSx


I'm generating a compressed file based on JSON data. Currently, when I generate a TAR file, my API works just fine. Here's my code:

app.post('/', (req, res) => {
  const filename = 'export.tar';

  const archive = archiver('tar', {});

  archive.on('warning', (err) => {
    console.log(`WARN -> ${err}`);
  });

  archive.on('error', (err) => {
    console.log(`ERROR -> ${err}`);
  });

  const files = req.body.files || [];
  for (const file of files) {
    archive.append(file.content, { name: file.name });
    console.log(`Appending ${file.name} file: ${JSON.stringify(file, null, 2)}`);
  }

  try {
    if (files.length > 0) {
      archive.pipe(res);
      archive.finalize();
      return res.attachment(filename);
    } else {
      return res.send({ error: 'No files to be downloaded' });
    }
  } catch (e) {
    return res.send({ error: e.toString() });
  }
});

And here is some sample JSON I pass through:

{
  "title": "Sample Title",
  "files": [
    {
      "name": "index.html",
      "content": "<p>Hello, World!</p>"
    },
    {
      "name": "README.md",
      "content": "# Hello, World!"
    }
  ]
}

But, when I change this to generate a ZIP file, I get errors like 21 "Is a directory" or 2 "No such file or directory."

The code I changed is:

  const filename = 'export.zip';
  const archive = archiver('zip', {
    zlib: { level: 9 },
  });

I've tried looking at other questions and issues, but haven't had any luck when making store: true or moving finalize() or making forceZip64: true. What can I change to make ZIP files work correctly?


Solution

  • This did the trick! By creating the output with createWriteStream and then piping the archive to that, we are able to download a zip that works perfectly.

    app.post('/', (req, res) => {
      const filename = 'export.zip';
    
      const archive = archiver('zip', {});
    
      let output = fs.createWriteStream(filename);
    
      archive.on('warning', err => {
        console.log(`WARN -> ${err}`);
      });
    
      archive.on('error', err => {
        console.log(`ERROR -> ${err}`);
      });
    
      output.on('end', function() {
        console.log('Data has been drained');
      });
      archive.pipe(output);
    
      const files = req.body.files || [];
      for (const file of files) {
        archive.append(file.content, { name: file.name });
        console.log(`Appending ${file.name} file: ${JSON.stringify(file, null, 2)}`);
      }
    
      try {
        if (files.length > 0) {
          archive.finalize();
          res.download(filename);
        } else {
          return res.send({ error: 'No files to be downloaded' });
        }
      } catch (e) {
        return res.send({ error: e.toString() });
      }
    });