Precisa compactar um diretório inteiro usando Node.js


107

Preciso compactar um diretório inteiro usando Node.js. No momento, estou usando o node-zip e cada vez que o processo é executado, ele gera um arquivo ZIP inválido (como você pode ver neste problema no Github ).

Existe outra opção melhor do Node.js que me permitirá compactar um diretório?

EDIT: Acabei usando arquivador

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 amostra para parâmetros:

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

ATUALIZAÇÃO: para quem pergunta sobre a implementação que usei, aqui está um link para meu downloader :


3
Alguém no Twitter sugeriu a API child_process e simplesmente chame o ZIP do sistema: nodejs.org/api/child_process.html
— commadelimited

1
Eu tentei a abordagem child_process. Tem duas advertências. 1) Ozip comando unix inclui toda a hierarquia de pasta pai do diretório de trabalho atual no arquivo compactado. Isso pode ser bom para você, não era para mim. Além disso, alterar o diretório de trabalho atual em child_process de alguma forma não afeta os resultados. 2) Para superar este problema, você deve usar o pushdpara pular para a pasta que irá compactar e zip -r, mas como pushd está embutido no bash e não no / bin / sh, você precisa usar / bin / bash também. No meu caso específico, isso não foi possível. Apenas um aviso.
— johnozbay

2
A child_process.execAPI do nó @johnozbay permite que você especifique o cwd de onde deseja executar o comando. Alterar o CWD corrige o problema da hierarquia da pasta pai. Ele também corrige o problema de não precisar pushd. Eu recomendo totalmente child_process.
— Govind Rai

1
stackoverflow.com/a/49970368/2757916 solução nodejs nativa usando child_process api. 2 linhas de código. Sem libs de terceiros.
— Govind Rai

@GovindRai Muito obrigado!
— johnozbay

Respostas:


124

Acabei usando o archiver lib. Funciona bem.

Exemplo

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();

1
Não parece haver nenhum exemplo de como fazer isso. Você se importa em compartilhar o que fez?
— Sinetheta

1
o arquivador, infelizmente, não suporta caracteres Unicode em nomes de arquivos a partir de agora. Reportado a github.com/ctalkington/node-archiver/issues/90 .
— Olho de

2
Como faço para incluir todos os arquivos e diretórios, recursivamente (também os arquivos / diretórios ocultos)?
— Ionică Bizău

12
O Archiver torna isso ainda mais simples agora. Em vez de usar o método bulk (), agora você pode usar o diretório (): npmjs.com/package/archiver#directory-dirpath-destpath-data
— Josh Feldman

14
.bulkestá obsoleto
— chovy

46

Não pretendo mostrar algo novo, só quero resumir as soluções acima para quem gosta de usar funções do Promise em seu código (como eu).

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 ajude alguém;)


o que exatamente está "fora" aqui? Presumo que a origem seja o caminho do diretório
— Dreams

@Tarun caminho completo do zip como: /User/mypc/mydir/test.zip
— D.Dimitrioglo

Não é possível descompactar o arquivo zip. Operação não permitida
— Jake

@ ekaj_03 certifique-se de que possui direitos suficientes para o diretório especificado
— D.Dimitrioglo

1
@ D.Dimitrioglo tudo de bom. Foi o problema do dir de origem. Obrigado :)
— Jake

17

Use a child_processAPI nativa do Node para fazer isso.

Não há necessidade de bibliotecas de terceiros. Duas linhas 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
});

Estou usando a API síncrona. Você pode usar child_process.exec(path, options, callback)se precisar assíncrono. Existem muito mais opções do que apenas especificar o CWD para ajustar ainda mais suas solicitações. Veja a documentação exec / execSync .


Observação: este exemplo assume que você tem o utilitário zip instalado em seu sistema (ele vem com o OSX, pelo menos). Alguns sistemas operacionais podem não ter o utilitário instalado (ou seja, o tempo de execução do AWS Lambda não tem). Nesse caso, você pode obter facilmente o binário do utilitário zip aqui e empacotá-lo junto com o código-fonte do seu aplicativo (para AWS Lambda, você pode empacotá-lo em uma camada Lambda também) ou terá que usar um módulo de terceiros (dos quais há muitos no NPM). Eu prefiro a abordagem anterior, já que o utilitário ZIP é testado e aprovado há décadas.


9
Infelizmente só funciona em sistemas que o tenham zip.
— janeiro de

3
Fui para esta solução apenas para evitar dezenas de bibliotecas externas no meu projeto
— EAzevedo

faz sentido, mas se não estou incorreto, isso é ferrar os usuários do Windows novamente. Por favor, pense nos usuários do Windows!
— Mathijs Segers

@MathijsSegers haha! é por isso que incluí um link para o binário para que os usuários do Windows também possam obtê-lo! :)
— Govind Rai

Existe uma maneira de fazer isso funcionar para um diretório dentro de um projeto em vez de um diretório de computador?
— Matt Croak

13

Archive.bulkagora está obsoleto, o novo método a ser usado para isso é 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();

2
Eles estavam se perguntando sobre isso, mas eles disseram que o bulk estava obsoleto, mas não sugeriram qual função usar.
— jarodsmk

1
Como você especifica o diretório "fonte"?
— Dreams

Tente uma vez a abordagem abaixo: jsonworld.wordpress.com/2019/09/07/…
— Soni Kumari

2020: archive.directory () é muito mais simples!
— OhadR


9

Esta é outra biblioteca que compacta a pasta em uma linha: zip-local

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

zipper.sync.zip("./hello/world/").compress().save("pack.zip");

4
Funcionou perfeitamente, ao contrário de dezenas de outros disponíveis na internet ou mencionados acima, que sempre geraram arquivo 'zero bytes' para mim
— Sergey Pleshakov

4

Para canalizar o resultado para o objeto de resposta (cenários em que é necessário fazer o download do zip em vez de armazená-lo localmente)

 archive.pipe(res);

As dicas de Sam para acessar o conteúdo do diretório funcionaram para mim.

src: ["**/*"]

3

O Adm-zip tem problemas apenas para compactar um arquivo existente https://github.com/cthackers/adm-zip/issues/64 , bem como corrupção com a compactação de arquivos binários.

Também tive problemas de corrupção de compressão com node-zip https://github.com/daraosn/node-zip/issues/4

node-archiver é o único que parece funcionar bem para compactar, mas não tem nenhuma funcionalidade de descompactação.


1
Sobre qual node-archiver você está falando? : github.com/archiverjs/node-archiver; github.com/richardbolt/node-archiver
— biphobe

@firian Ele não disse Archiver, ele disse Adm-zip.
— Francis Pelland

5
@FrancisPelland Umm, na última frase ele escreveu " node-archiver é o único que parece funcionar " - é a isso que estou me referindo.
— biphobe

acho que ele comeu npmjs.com/package/archiver
— OhadR

2

Encontrei esta pequena biblioteca que contém o que você precisa.

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


É possível adicionar parâmetros para fazer a pasta zip? como nível compactado e tamanho, se sim, como fazer isso?
— Trang D

1

Como archivernão é compatível com a nova versão do webpack há muito tempo, recomendo o uso do 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);
});

0

Você pode tentar de uma maneira simples:

Instale zip-dir:

npm install zip-dir

e usá-lo

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

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

    });

é possível adicionar parâmetros para fazer a pasta zip? como nível compactado e tamanho, se sim, como fazer isso?
— Trang D

0

Acabei envolvendo o arquivador para emular JSZip, pois a refatoração por meio do meu projeto exigiria muito esforço. Eu entendo que Archiver pode não ser a melhor escolha, mas aqui está.

// 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);
    }
};
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.