From adfb3fe499433f691cb292753b3baf009b28217e Mon Sep 17 00:00:00 2001 From: Hannah Wolfe Date: Tue, 7 Apr 2020 09:53:28 +0100 Subject: [PATCH] Consistent interface and response - Creating a better, more long-term API here - compress and extract are opposite, neat terms - Use new Promise to get rid of callback argument when using archiver to compress a folder - Add options argument, and make a couple of key details configurable - Make the response intelligable - Ensure both functions are consistent - Updated tests to match --- ghost/zip/README.md | 13 ++++- ghost/zip/index.js | 7 +-- ghost/zip/lib/compress.js | 50 +++++++++++++++++++ ghost/zip/lib/extract.js | 25 ++++++++++ ghost/zip/lib/zip-folder.js | 30 ----------- .../test/{zip-folder.test.js => zip.test.js} | 19 ++++--- 6 files changed, 102 insertions(+), 42 deletions(-) create mode 100644 ghost/zip/lib/compress.js create mode 100644 ghost/zip/lib/extract.js delete mode 100644 ghost/zip/lib/zip-folder.js rename ghost/zip/test/{zip-folder.test.js => zip.test.js} (71%) diff --git a/ghost/zip/README.md b/ghost/zip/README.md index ebf7609115..bd849b9608 100644 --- a/ghost/zip/README.md +++ b/ghost/zip/README.md @@ -11,6 +11,17 @@ or ## Usage +``` +const zip = require('@tryghost/zip'); + +// Create a zip from a folder + +let res = await zip.compress('path/to/a/folder', 'path/to/archive.zip', [options]) + +// Extract a zip to a folder + +let res = await zip.extract('path/to/archive.zip', 'path/to/files', [options]) +``` ## Develop @@ -34,6 +45,6 @@ Follow the instructions for the top-level repo. -# Copyright & License +# Copyright & License Copyright (c) 2020 Ghost Foundation - Released under the [MIT license](LICENSE). \ No newline at end of file diff --git a/ghost/zip/index.js b/ghost/zip/index.js index 4024126cd2..75700f7c5d 100644 --- a/ghost/zip/index.js +++ b/ghost/zip/index.js @@ -1,7 +1,4 @@ -const extract = require('extract-zip'); -const zipFolder = require('./lib/zip-folder'); - module.exports = { - extract, - zipFolder + extract: require('./lib/extract'), + compress: require('./lib/compress') }; diff --git a/ghost/zip/lib/compress.js b/ghost/zip/lib/compress.js new file mode 100644 index 0000000000..05dad46cb4 --- /dev/null +++ b/ghost/zip/lib/compress.js @@ -0,0 +1,50 @@ +const fs = require('fs-extra'); +const Promise = require('bluebird'); + +const defaultOptions = { + type: 'zip', + glob: '**/*', + ignore: ['node_modules/**'] +}; + +/** + * Compress + * + * - Create a zip file from a folder + * + * @param {String} folderToZip - full path to the folder to be zipped + * @param {String} destination - full path to the resulting zip file + * @param {Object} [options] + * @param {String} options.type - zip by default see archiver for other options + * @param {String} options.glob - the files to include, defaults to all files and folders + * @param {Array} options.ignore - any paths that should be ignored, sets node_modules by default + */ +module.exports = (folderToZip, destination, options = {}) => { + const opts = Object.assign({}, defaultOptions, options); + + const archiver = require('archiver'); + const output = fs.createWriteStream(destination); + const archive = archiver.create(opts.type, {}); + + return new Promise((resolve, reject) => { + // If folder to zip is a symlink, we want to get the target + // of the link and zip that instead of zipping the symlink + if (fs.lstatSync(folderToZip).isSymbolicLink()) { + folderToZip = fs.realpathSync(folderToZip); + } + + output.on('close', function () { + resolve({path: destination, size: archive.pointer()}); + }); + + archive.on('error', function (err) { + reject(err); + }); + archive.glob(opts.glob, { + cwd: folderToZip, + ignore: opts.ignore + }); + archive.pipe(output); + archive.finalize(); + }); +}; diff --git a/ghost/zip/lib/extract.js b/ghost/zip/lib/extract.js new file mode 100644 index 0000000000..55009e9ed6 --- /dev/null +++ b/ghost/zip/lib/extract.js @@ -0,0 +1,25 @@ +const defaultOptions = {}; + +/** + * Extract + * + * - Unzip an archive to a folder + * + * @param {String} zipToExtract - full path to zip file that should be extracted + * @param {String} destination - full path of the extraction target + * @param {Object} [options] + * @param {Integer} options.defaultDirMode - Directory Mode (permissions), defaults to 0o755 + * @param {Integer} options.defaultFileMode - File Mode (permissions), defaults to 0o644 + * @param {Function} options.onEntry - if present, will be called with (entry, zipfile) for every entry in the zip + */ +module.exports = (zipToExtract, destination, options) => { + const opts = Object.assign({}, defaultOptions, options); + + const extract = require('extract-zip'); + + opts.dir = destination; + + return extract(zipToExtract, opts).then(() => { + return {path: destination}; + }); +}; diff --git a/ghost/zip/lib/zip-folder.js b/ghost/zip/lib/zip-folder.js deleted file mode 100644 index 045e12af5c..0000000000 --- a/ghost/zip/lib/zip-folder.js +++ /dev/null @@ -1,30 +0,0 @@ -const fs = require('fs-extra'); -const Promise = require('bluebird'); - -const zipFolder = (folderToZip, destination, callback) => { - var archiver = require('archiver'), - output = fs.createWriteStream(destination), - archive = archiver.create('zip', {}); - - // If folder to zip is a symlink, we want to get the target - // of the link and zip that instead of zipping the symlink - if (fs.lstatSync(folderToZip).isSymbolicLink()) { - folderToZip = fs.realpathSync(folderToZip); - } - - output.on('close', function () { - callback(null, archive.pointer()); - }); - - archive.on('error', function (err) { - callback(err, null); - }); - archive.glob(`**/*`, { - cwd: folderToZip, - ignore: ['node_modules/**'] - }); - archive.pipe(output); - archive.finalize(); -}; - -module.exports = Promise.promisify(zipFolder); diff --git a/ghost/zip/test/zip-folder.test.js b/ghost/zip/test/zip.test.js similarity index 71% rename from ghost/zip/test/zip-folder.test.js rename to ghost/zip/test/zip.test.js index 64c4951e8a..134521f1ac 100644 --- a/ghost/zip/test/zip-folder.test.js +++ b/ghost/zip/test/zip.test.js @@ -6,9 +6,9 @@ const path = require('path'); const fs = require('fs-extra'); // Mimic how we expect this to be required -const {zipFolder, extract} = require('../'); +const {compress, extract} = require('../'); -describe('lib/fs: read csv', function () { +describe('Compress and Extract should be opposite functions', function () { let symlinkPath, folderToSymlink, zipDestination, unzipDestination; const cleanUp = () => { @@ -33,10 +33,17 @@ describe('lib/fs: read csv', function () { it('ensure symlinks work', function (done) { fs.symlink(folderToSymlink, symlinkPath); - zipFolder(symlinkPath, zipDestination) - .then(() => { - extract(zipDestination, {dir: unzipDestination}) - .then(() => { + compress(symlinkPath, zipDestination) + .then((res) => { + res.should.be.an.Object().with.properties('path', 'size'); + res.path.should.eql(zipDestination); + res.size.should.eql(321775); + + extract(zipDestination, unzipDestination) + .then((res) => { + res.should.be.an.Object().with.properties('path'); + res.path.should.eql(unzipDestination); + fs.readdir(unzipDestination, function (err, files) { if (err) { return done(err);