Rename files inside zip archive in nodejs
Asked Answered
P

3

6

I am writing a nodejs script which should do the following:

  1. Download a zip file
  2. Remove the top level directory of the zip file (moving all files one folder up)
  3. Upload the new zip file

Because the zip file is rather large, I would like to rename (or move) the files without unzipping and rezipping the file.

Is that possible?

Petrous answered 9/6, 2016 at 16:42 Comment(0)
G
4

Yes, it's possible Using a library like adm-zip

var AdmZip = require('adm-zip');

//create a zip object to hold the new zip files
var newZip = new AdmZip();

// reading archives
var zip = new AdmZip('somePath/download.zip');
var zipEntries = zip.getEntries(); // an array of ZipEntry records

zipEntries.forEach(function(zipEntry) {
    var fileName = zipEntry.entryName;
    var fileContent = zip.readAsText(fileName)
    //Here remove the top level directory
    var newFileName = fileName.substring(fileName.indexOf("/") + 1);

    newZip.addFile(newFileName, fileContent, '', 0644 << 16);        
});

newZip.writeZip('somePath/upload.zip');  //write the new zip 

Algorithm

Create a newZip object to temporarily hold files in memory Read all entries in the downloaded zip. For each entry

  1. Read the fileName. This includes the path
  2. Read the file content using the fileName
  3. Remove the top level directory name to get the newFileName
  4. Add the fileContent in step 2 to the newZip giving it the newFileName from step 3
  5. Finally, write out the newZip to disk giving it a new zipName

Hope that helps

Giaimo answered 28/6, 2016 at 7:57 Comment(0)
E
2

You can use the great jszip library with async-promise style.

import jszip from 'jszip';
import fs from 'fs';

/**
 * Move/rename entire directory tree within a zip.
 * @param {*} zipFilePath The original zip file
 * @param {*} modifiedZipFilePath The path where palace the modified zip 
 * @param {*} originalDir The original directory to change
 * @param {*} destinationDir The new directory to move to.
 */
async function moveDirectory(zipFilePath, modifiedZipFilePath, originalDir, destinationDir) {

    // Read zip file bits buffer
    const zipFileBuffer = await fs.promises.readFile(zipFilePath);
    // Load jszip instance
    const zipFile = await jszip.loadAsync(zipFileBuffer);
    // Get the original directory entry
    const originalDirContent = zipFile.folder(originalDir);
    // Walk on all directory tree
    originalDirContent.forEach((path, entry) => {
        // If it's a directory entry ignore it.
        if (entry.dir) {
            return;
        }
        // Extract the file path within the directory tree 
        const internalDir = path.split(originalDir)[0];
        // Build the new file directory in the new tree 
        const newFileDir = `${destinationDir}/${internalDir}`;
        // Put the file in the new tree, with the same properties
        zipFile.file(newFileDir, entry.nodeStream(), { 
            createFolders: true,
            unixPermissions: entry.unixPermissions,
            comment: entry.comment,
            date: entry.date,
        });
    });
    // After all files copied to the new tree, remove the original directory tree.
    zipFile.remove(originalDir);
    // Generate the new zip buffer
    const modifiedZipBuffer = await zipFile.generateAsync({ type: 'nodebuffer' });
    // Save the buffer as a new zip file
    await fs.promises.writeFile(modifiedZipFilePath, modifiedZipBuffer);
}

moveDirectory('archive.zip', 'modified.zip', 'some-dir/from-dir', 'some-other-dir/to-dir');

This is simply walking on all original directory tree entries and place them in the new directory tree.

Excogitate answered 20/5, 2021 at 9:8 Comment(0)
S
0

Creating an answer of an edit of Vitalis' answer, as there are too many edits pending.

const AdmZip = require('adm-zip');

// create a zip object to hold the new zip files
const newZip = new AdmZip();

// read existing zip
const oldZip = new AdmZip('somePath/download.zip');
const zipEntries = oldZip.getEntries(); // an array of ZipEntry records

zipEntries.forEach(function(zipEntry) {
  let oldEntryName = zipEntry.entryName;
  let fileContent = oldZip.readFile(oldEntryName) || Buffer.alloc(0);
  // remove the top level directory
  let newEntryName = oldEntryName.substring(oldEntryName.indexOf("/") + 1);

  newZip.addFile(newEntryName, fileContent);        
});

newZip.writeZip('somePath/upload.zip');  //write the new zip

The comment and attr parameters are optional for newZip.addFile(). In fact, 0644 << 16 should be written as 0o644 << 16 and it also denies you access to read the file when the zip file is extracted.

fileContent needs to be a buffer and not a string. A fallback is given (Buffer.alloc(0)) in case, in the unlikely event, the entry suspiciously disappears from the old zip file.

Sylvie answered 22/6, 2023 at 14:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.