Necesita ZIP un directorio completo usando Node.js

107

Necesito comprimir un directorio completo usando Node.js. Actualmente estoy usando node-zip y cada vez que se ejecuta el proceso genera un archivo ZIP no válido (como puede ver en este problema de Github ).

¿Hay otra opción de Node.js mejor que me permita comprimir un directorio?

EDITAR: terminé usando el archivador

writeZip = function(dir,name) {
var zip = new JSZip(),
    code = zip.folder(dir),
    output = zip.generate(),
    filename = ['jsd-',name,'.zip'].join('');

fs.writeFileSync(baseDir + filename, output);
console.log('creating ' + filename);
};

valor de muestra para los parámetros:

dir = /tmp/jsd-<randomstring>/
name = <randomstring>

ACTUALIZACIÓN: Para aquellos que preguntan sobre la implementación que utilicé, aquí hay un enlace a mi descargador :

separado por comas
fuente
3
Alguien en Twitter sugirió la API child_process, y simplemente llame al sistema ZIP: nodejs.org/api/child_process.html
commadelimited
1
Probé el enfoque child_process. Tiene dos salvedades. 1) Elzip comando Unix incluye toda la jerarquía de carpetas principales del directorio de trabajo actual en el archivo comprimido. Esto podría estar bien para ti, no lo fue para mí. Además, cambiar el directorio de trabajo actual en child_process de alguna manera no afecta los resultados. 2) Para superar este problema, debe usar pushdpara saltar a la carpeta que comprimirá y zip -r, pero como pushd está integrado en bash y no en / bin / sh, también debe usar / bin / bash. En mi caso específico, esto no fue posible. Solo un aviso.
johnozbay
2
La child_process.execapi del nodo de @johnozbay le permite especificar el cwd desde donde desea ejecutar el comando. Cambiar el CWD soluciona el problema de la jerarquía de la carpeta principal. También soluciona el problema de no necesitar pushd. Recomiendo completamente child_process.
Govind Rai
1
stackoverflow.com/a/49970368/2757916 solución nativa de nodejs usando la api child_process. 2 líneas de código. Sin bibliotecas de terceros.
Govind Rai
@GovindRai ¡Muchas gracias!
johnozbay

Respuestas:

124

Terminé usando archiver lib. Funciona genial.

Ejemplo

var file_system = require('fs');
var archiver = require('archiver');

var output = file_system.createWriteStream('target.zip');
var archive = archiver('zip');

output.on('close', function () {
    console.log(archive.pointer() + ' total bytes');
    console.log('archiver has been finalized and the output file descriptor has closed.');
});

archive.on('error', function(err){
    throw err;
});

archive.pipe(output);

// append files from a sub-directory and naming it `new-subdir` within the archive (see docs for more options):
archive.directory(source_dir, false);
archive.finalize();
separado por comas
fuente
1
No parece haber ningún ejemplo de cómo hacer esto, ¿te importaría compartir lo que hiciste?
Sinetheta
1
El archivador, desafortunadamente, no admite caracteres Unicode en los nombres de archivo a partir de ahora. Reportado a github.com/ctalkington/node-archiver/issues/90 .
Ojo
2
¿Cómo incluyo todos los archivos y directorios, de forma recursiva (también los archivos / directorios ocultos)?
Ionică Bizău
12
Archiver hace que esto sea aún más simple ahora. En lugar de usar el método bulk (), ahora puede usar directory (): npmjs.com/package/archiver#directory-dirpath-destpath-data
Josh Feldman
14
.bulkestá obsoleto
Chovy
46

No pretendo mostrar algo nuevo, solo quiero resumir las soluciones anteriores para aquellos a quienes les gusta usar las funciones de Promise en su código (como yo).

const archiver = require('archiver');

/**
 * @param {String} source
 * @param {String} out
 * @returns {Promise}
 */
function zipDirectory(source, out) {
  const archive = archiver('zip', { zlib: { level: 9 }});
  const stream = fs.createWriteStream(out);

  return new Promise((resolve, reject) => {
    archive
      .directory(source, false)
      .on('error', err => reject(err))
      .pipe(stream)
    ;

    stream.on('close', () => resolve());
    archive.finalize();
  });
}

Espero que ayude a alguien;)

D.Dimitrioglo
fuente
¿Qué es exactamente "afuera" aquí? Supongo que la fuente es la ruta del directorio
Dreams
Ruta de @Tarun full zip como: /User/mypc/mydir/test.zip
D.Dimitrioglo
No se puede descomprimir el archivo zip. Operación no permitida
Jake
@ ekaj_03 asegúrese de tener suficientes derechos para el directorio especificado
D.Dimitrioglo
1
@ D.Dimitrioglo todo bien. Fue el problema del directorio de origen. Gracias :)
Jake
17

Utilice la child_processAPI nativa de Node para lograr esto.

No se necesitan bibliotecas de terceros. Dos líneas de código.

const child_process = require("child_process");
child_process.execSync(`zip -r DESIRED_NAME_OF_ZIP_FILE_HERE *`, {
  cwd: PATH_TO_FOLDER_YOU_WANT_ZIPPED_HERE
});

Estoy usando la API síncrona. Puede usar child_process.exec(path, options, callback)si necesita async. Hay muchas más opciones además de especificar el CWD para afinar aún más sus solicitudes. Consulte los documentos exec / execSync .


Tenga en cuenta: Este ejemplo asume que tiene la utilidad zip instalada en su sistema (viene con OSX, al menos). Es posible que algunos sistemas operativos no tengan la utilidad instalada (es decir, el tiempo de ejecución de AWS Lambda no). En ese caso, puede obtener fácilmente el binario de la utilidad zip aquí y empaquetarlo junto con el código fuente de su aplicación (para AWS Lambda también puede empaquetarlo en una Capa Lambda), o tendrá que usar un módulo de terceros (de los cuales hay muchos en NPM). Prefiero el enfoque anterior, ya que la utilidad ZIP se ha probado y probado durante décadas.

Govind Rai
fuente
9
Desafortunadamente solo funciona en sistemas que tienen zip.
Janpio
3
Fui a esta solución solo para evitar docenas de bibliotecas externas en mi proyecto
EAzevedo
Tiene sentido, pero si no me equivoco, esto es molestar a los usuarios de Windows nuevamente. ¡Piense en los usuarios de Windows!
Mathijs Segers
@MathijsSegers jaja! por eso incluí un enlace al binario para que los usuarios de Windows puedan obtenerlo también. :)
Govind Rai
¿Hay alguna forma de hacer que esto funcione para un directorio dentro de un proyecto en lugar de un directorio de computadora?
Matt Croak
13

Archive.bulkahora está en desuso, el nuevo método que se utilizará para esto es glob :

var fileName =   'zipOutput.zip'
var fileOutput = fs.createWriteStream(fileName);

fileOutput.on('close', function () {
    console.log(archive.pointer() + ' total bytes');
    console.log('archiver has been finalized and the output file descriptor has closed.');
});

archive.pipe(fileOutput);
archive.glob("../dist/**/*"); //some glob pattern here
archive.glob("../dist/.htaccess"); //another glob pattern
// add as many as you like
archive.on('error', function(err){
    throw err;
});
archive.finalize();
caiocpricci2
fuente
2
Me preguntaba acerca de esto, dijeron que el volumen estaba en desuso, pero no sugirieron qué función usar en su lugar.
jarodsmk
1
¿Cómo se especifica el directorio "fuente"?
Sueños
Pruebe una vez el siguiente enfoque: jsonworld.wordpress.com/2019/09/07/…
Soni Kumari
2020: archive.directory () es mucho más simple.
OhadR
9

Para incluir todos los archivos y directorios:

archive.bulk([
  {
    expand: true,
    cwd: "temp/freewheel-bvi-120",
    src: ["**/*"],
    dot: true
  }
]);

Utiliza node-glob ( https://github.com/isaacs/node-glob ) debajo, por lo que cualquier expresión coincidente compatible con eso funcionará.

Sam Ghaderyan
fuente
.bulk está en desuso
Mohamad Hamouday
9

Esta es otra biblioteca que comprime la carpeta en una línea: zip-local

var zipper = require('zip-local');

zipper.sync.zip("./hello/world/").compress().save("pack.zip");
Sueños
fuente
4
Funcionó como un encanto, a diferencia de una docena de otros disponibles en Internet o mencionados anteriormente, que siempre generaron un archivo de 'cero bytes' para mí
Sergey Pleshakov
4

Para canalizar el resultado al objeto de respuesta (escenarios donde es necesario descargar el zip en lugar de almacenarlo localmente)

 archive.pipe(res);

Las sugerencias de Sam para acceder al contenido del directorio funcionaron para mí.

src: ["**/*"]
Raf
fuente
3

Adm-zip tiene problemas al comprimir un archivo existente https://github.com/cthackers/adm-zip/issues/64 , así como la corrupción al comprimir archivos binarios.

También me encontré con problemas de corrupción de compresión con node-zip https://github.com/daraosn/node-zip/issues/4

node-archiver es el único que parece funcionar bien para comprimir, pero no tiene ninguna funcionalidad de descompresión.

Xiaoxin
fuente
1
¿De qué nodo-archivador estás hablando? : github.com/archiverjs/node-archiver; github.com/richardbolt/node-archiver
biphobe
@firian No dijo Archiver, dijo Adm-zip.
Francis Pelland
5
@FrancisPelland Umm, en la última oración escribió " node-archiver es el único que parece funcionar ", a eso me refiero.
biphobe
Creo que él hizo npmjs.com/package/archiver
OhadR
2

Encontré esta pequeña biblioteca que encapsula lo que necesitas.

npm install zip-a-folder

const zip-a-folder = require('zip-a-folder');
await zip-a-folder.zip('/path/to/the/folder', '/path/to/archive.zip');

https://www.npmjs.com/package/zip-a-folder

Ondrej Kvasnovsky
fuente
¿Es posible agregar parámetros para hacer una carpeta zip? como nivel comprimido y tamaño si es así, ¿cómo se hace?
Trang D
1

Dado archiverque no es compatible con la nueva versión de webpack durante mucho tiempo, recomiendo usar zip-lib .

var zl = require("zip-lib");

zl.archiveFolder("path/to/folder", "path/to/target.zip").then(function () {
    console.log("done");
}, function (err) {
    console.log(err);
});
tao
fuente
0

Puedes probar de forma sencilla:

Instalar zip-dir:

npm install zip-dir

y usarlo

var zipdir = require('zip-dir');

let foldername =  src_path.split('/').pop() 
    zipdir(<<src_path>>, { saveTo: 'demo.zip' }, function (err, buffer) {

    });
Harsha Biyani
fuente
¿Es posible agregar parámetros para hacer una carpeta zip? como nivel comprimido y tamaño si es así, ¿cómo se hace?
Trang D
0

Terminé envolviendo el archivador para emular JSZip, ya que refactorizar a través de mi proyecto tomaría demasiado esfuerzo. Entiendo que Archiver no sea la mejor opción, pero aquí tienes.

// USAGE:
const zip=JSZipStream.to(myFileLocation)
    .onDone(()=>{})
    .onError(()=>{});

zip.file('something.txt','My content');
zip.folder('myfolder').file('something-inFolder.txt','My content');
zip.finalize();

// NodeJS file content:
    var fs = require('fs');
    var path = require('path');
    var archiver = require('archiver');

  function zipper(archive, settings) {
    return {
        output: null,
        streamToFile(dir) {
            const output = fs.createWriteStream(dir);
            this.output = output;
            archive.pipe(output);

            return this;
        },
        file(location, content) {
            if (settings.location) {
                location = path.join(settings.location, location);
            }
            archive.append(content, { name: location });
            return this;
        },
        folder(location) {
            if (settings.location) {
                location = path.join(settings.location, location);
            }
            return zipper(archive, { location: location });
        },
        finalize() {
            archive.finalize();
            return this;
        },
        onDone(method) {
            this.output.on('close', method);
            return this;
        },
        onError(method) {
            this.output.on('error', method);
            return this;
        }
    };
}

exports.JSzipStream = {
    to(destination) {
        console.log('stream to',destination)
        const archive = archiver('zip', {
            zlib: { level: 9 } // Sets the compression level.
        });
        return zipper(archive, {}).streamToFile(destination);
    }
};
usuario672770
fuente