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 :

limitado por comando
fonte
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();
limitado por comando
fonte
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;)

D.Dimitrioglo
fonte
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.

Govind Rai
fonte
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();
caiocpricci2
fonte
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

Para incluir todos os arquivos e diretórios:

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

Ele usa node-glob ( https://github.com/isaacs/node-glob ) embaixo, então qualquer expressão compatível com isso funcionará.

Sam Ghaderyan
fonte
.bulk foi descontinuado
Mohamad Hamouday
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");
Sonhos
fonte
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: ["**/*"]
Raf
fonte
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.

Xiaoxin
fonte
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

Ondrej Kvasnovsky
fonte
É 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);
});
tao
fonte
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) {

    });
Harsha Biyani
fonte
é 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);
    }
};
user672770
fonte