-
Notifications
You must be signed in to change notification settings - Fork 142
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: (strf-8684) update 'tmp' package
- Loading branch information
MaxGenash
committed
Sep 22, 2020
1 parent
e0b6387
commit b1e932c
Showing
11 changed files
with
289 additions
and
337 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
/** | ||
* @module Contains functions for working with archives | ||
*/ | ||
|
||
const yauzl = require('yauzl'); | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
const { promisify } = require('util'); | ||
|
||
const { readFromStream } = require('./utils/asyncUtils'); | ||
|
||
/** | ||
* @param {object} options | ||
* @param {string} options.zipPath | ||
* @param {string} [options.fileToExtract] - filename to download only | ||
* @param {string[]} [options.exclude] - paths of files and directories to exclude | ||
* @param {object} [options.outputNames] - new names for some files. Format: { 'oldName1': 'newName1', ...} | ||
* @returns {Promise<void>} | ||
*/ | ||
async function extractZipFiles({ zipPath, fileToExtract, exclude, outputNames = {}}) { | ||
let foundMatch = false; | ||
|
||
const zipFile = await promisify(yauzl.open)(zipPath, { lazyEntries: true }); | ||
|
||
await new Promise((resolve, reject) => { | ||
zipFile.on('entry', async entry => { | ||
try { | ||
const readableStream = await promisify(zipFile.openReadStream.bind(zipFile))(entry); | ||
|
||
if (fileToExtract) { | ||
if (fileToExtract !== entry.fileName) { | ||
return zipFile.readEntry(); | ||
} | ||
foundMatch = true; | ||
} else if (exclude && exclude.length) { | ||
// Do not process any file or directory within the exclude option | ||
for (const excludeItem of exclude) { | ||
if (entry.fileName.startsWith(excludeItem)) { | ||
return zipFile.readEntry(); | ||
} | ||
} | ||
} | ||
|
||
// If file is a directory, then move to next | ||
if (/\/$/.test(entry.fileName)) { | ||
return zipFile.readEntry(); | ||
} | ||
|
||
// Create a directory if the parent directory does not exists | ||
const parsedPath = path.parse(entry.fileName); | ||
|
||
if (parsedPath.dir && !fs.existsSync(parsedPath.dir)) { | ||
fs.mkdirSync(parsedPath.dir, { recursive: true }); | ||
} | ||
|
||
let fileData = await readFromStream(readableStream); | ||
if (entry.fileName.endsWith('.json')) { | ||
// Make sure that the JSON file if valid | ||
fileData = JSON.stringify(JSON.parse(fileData), null, 2); | ||
} | ||
|
||
const outputFileName = outputNames[entry.fileName] || entry.fileName; | ||
await promisify(fs.writeFile)(outputFileName, fileData, { flag: 'w+' }); | ||
|
||
// Close read if the requested file is found | ||
if (fileToExtract) { | ||
zipFile.close(); | ||
return resolve(); | ||
} | ||
|
||
zipFile.readEntry(); | ||
} catch (err) { | ||
return reject(err); | ||
} | ||
}); | ||
|
||
zipFile.readEntry(); | ||
|
||
zipFile.once('end', function () { | ||
zipFile.close(); | ||
|
||
if (!foundMatch && fileToExtract) { | ||
return reject(new Error(`${fileToExtract} not found`)); | ||
} | ||
|
||
resolve(); | ||
}); | ||
}); | ||
} | ||
|
||
|
||
module.exports = { | ||
extractZipFiles, | ||
}; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
const fs = require('fs'); | ||
const Path = require('path'); | ||
const yauzl = require('yauzl'); | ||
|
||
const { extractZipFiles } = require('./archiveManager'); | ||
|
||
describe('archiveManager', () => { | ||
describe('extractZipFiles', () => { | ||
// We run tests for a real archive containing 2 files: | ||
// - config.json | ||
// - schema.json | ||
let zipPath = Path.join(process.cwd(), 'test', '_mocks', 'themes', 'valid', 'mock-theme.zip'); | ||
let fsWriteSub; | ||
let yauzlOpenSpy; | ||
|
||
beforeEach(() => { | ||
jest.spyOn(console, 'log').mockImplementation(jest.fn()); | ||
|
||
fsWriteSub = jest.spyOn(fs, 'writeFile').mockImplementation((name, config, options, callback) => { | ||
callback(false); | ||
}); | ||
}); | ||
|
||
afterEach(() => { | ||
jest.resetAllMocks(); | ||
jest.restoreAllMocks(); | ||
}); | ||
|
||
beforeEach(() => { | ||
yauzlOpenSpy = jest.spyOn(yauzl, 'open'); | ||
}); | ||
|
||
it('should call yauzl.open with the passed zipPath', async () => { | ||
await extractZipFiles({ zipPath }); | ||
|
||
expect(yauzlOpenSpy).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it('should save all the files from the zip archive taking into account options.outputNames', async () => { | ||
const newConfigName = 'config2.json'; | ||
const outputNames = { 'config.json': newConfigName }; | ||
await extractZipFiles({ zipPath, outputNames }); | ||
|
||
expect(fsWriteSub).toHaveBeenCalledTimes(2); | ||
expect(fsWriteSub).toHaveBeenCalledWith( | ||
'schema.json', expect.anything(), expect.objectContaining({ flag: 'w+' }), expect.any(Function), | ||
); | ||
expect(fsWriteSub).toHaveBeenCalledWith( | ||
newConfigName, expect.anything(), expect.objectContaining({ flag: 'w+' }), expect.any(Function), | ||
); | ||
}); | ||
|
||
it('should not save files specified in options.exclude', async () => { | ||
const exclude = ['config.json']; | ||
await extractZipFiles({ zipPath, exclude }); | ||
|
||
expect(fsWriteSub).toHaveBeenCalledTimes(1); | ||
expect(fsWriteSub).toHaveBeenCalledWith( | ||
'schema.json', expect.anything(), expect.objectContaining({ flag: 'w+' }), expect.any(Function), | ||
); | ||
}); | ||
|
||
it('should save the file specified in options.fileToExtract only', async () => { | ||
const fileToExtract = 'config.json'; | ||
await extractZipFiles({ zipPath, fileToExtract }); | ||
|
||
expect(fsWriteSub).toHaveBeenCalledTimes(1); | ||
expect(fsWriteSub).toHaveBeenCalledWith( | ||
fileToExtract, expect.anything(), expect.objectContaining({ flag: 'w+' }), expect.any(Function), | ||
); | ||
}); | ||
|
||
it('should throw an error when the file with name options.fileToExtract was not found', async () => { | ||
const fileToExtract = 'I dont exist.txt'; | ||
|
||
await expect( | ||
extractZipFiles({ zipPath, fileToExtract }), | ||
).rejects.toThrow(/not found/); | ||
|
||
expect(fsWriteSub).toHaveBeenCalledTimes(0); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,123 +1,28 @@ | ||
const fetch = require('node-fetch'); | ||
const yauzl = require('yauzl'); | ||
const fs = require('fs'); | ||
const tmp = require('tmp'); | ||
const path = require('path'); | ||
const tmp = require('tmp-promise'); | ||
const { extractZipFiles } = require('./archiveManager'); | ||
const { fetchFile } = require('./utils/networkUtils'); | ||
|
||
const utils = {}; | ||
|
||
module.exports = utils; | ||
|
||
utils.downloadThemeFiles = (options, callback) => { | ||
tmp.file(function _tempFileCreated(err, tempThemePath, fd, cleanupCallback) { | ||
if (err) { | ||
callback(err); | ||
} | ||
|
||
Promise.resolve() | ||
.then(() => fetch(options.downloadUrl)) | ||
.then(response => new Promise((resolve, reject) => { | ||
if (!response.ok) { | ||
reject(`Unable to download theme files from ${options.downloadUrl}: ${response.statusText}`); | ||
} | ||
|
||
response.body.pipe(fs.createWriteStream(tempThemePath)) | ||
.on('finish', () => resolve(tempThemePath)) | ||
.on('error', reject); | ||
})) | ||
.then(tempThemePath => new Promise((resolve, reject) => { | ||
let foundMatch = false; | ||
|
||
console.log('ok'.green + ' -- Theme files downloaded'); | ||
console.log('ok'.green + ' -- Extracting theme files'); | ||
|
||
yauzl.open(tempThemePath, {lazyEntries: true}, (error, zipFile) => { | ||
if (error) { | ||
return reject(error); | ||
} | ||
|
||
zipFile.on('entry', entry => { | ||
zipFile.openReadStream(entry, (readStreamError, readStream) => { | ||
if (readStreamError) { | ||
return reject(readStreamError); | ||
} | ||
|
||
let configFileData = ''; | ||
|
||
if (options.file && options.file.length) { | ||
if (options.file !== entry.fileName) { | ||
zipFile.readEntry(); | ||
return; | ||
} | ||
foundMatch = true; | ||
} else if (options.exclude && options.exclude.length) { | ||
// Do not process any file or directory within the exclude option | ||
for (const excludeItem of options.exclude) { | ||
if (entry.fileName.startsWith(excludeItem)) { | ||
zipFile.readEntry(); | ||
return; | ||
} | ||
} | ||
} | ||
|
||
// Create a directory if the parent directory does not exists | ||
const parsedPath = path.parse(entry.fileName); | ||
|
||
if (parsedPath.dir && !fs.existsSync(parsedPath.dir)) { | ||
fs.mkdirSync(parsedPath.dir, {recursive: true}); | ||
} | ||
|
||
// If file is a directory, then move to next | ||
if (/\/$/.test(entry.fileName)) { | ||
zipFile.readEntry(); | ||
return; | ||
} | ||
|
||
readStream.on('end', () => { | ||
if (entry.fileName.endsWith('.json')) { | ||
configFileData = JSON.stringify(JSON.parse(configFileData), null, 2); | ||
} | ||
utils.downloadThemeFiles = async options => { | ||
const { path: tempThemePath, cleanup } = await tmp.file(); | ||
|
||
fs.writeFile(entry.fileName, configFileData, {flag: 'w+'}, error => { | ||
if (error) { | ||
reject(error); | ||
} | ||
try { | ||
await fetchFile(options.downloadUrl, tempThemePath); | ||
} catch (err) { | ||
throw new Error(`Unable to download theme files from ${options.downloadUrl}: ${err.message}`); | ||
} | ||
|
||
// Close read if file requested is found | ||
if (options.file && options.file.length) { | ||
console.log('ok'.green + ' -- Theme files extracted'); | ||
zipFile.close(); | ||
resolve(options); | ||
} else { | ||
zipFile.readEntry(); | ||
} | ||
}); | ||
}); | ||
console.log('ok'.green + ' -- Theme files downloaded'); | ||
console.log('ok'.green + ' -- Extracting theme files'); | ||
|
||
readStream.on('data', chunk => { | ||
configFileData += chunk; | ||
}); | ||
}); | ||
}); | ||
await extractZipFiles({ zipPath: tempThemePath, fileToExtract: options.file, exclude: options.exclude }); | ||
|
||
zipFile.readEntry(); | ||
console.log('ok'.green + ' -- Theme files extracted'); | ||
|
||
zipFile.once('end', function () { | ||
if (!foundMatch && (options.file && options.file.length)) { | ||
console.log('Warning'.yellow + ` -- ${options.file} not found!`); | ||
reject(`${options.file} not found`); | ||
return; | ||
} | ||
await cleanup(); | ||
|
||
console.log('ok'.green + ' -- Theme files extracted'); | ||
zipFile.close(); | ||
resolve(options); | ||
}); | ||
}); | ||
})) | ||
.then(() => { | ||
cleanupCallback(); | ||
callback(null, options); | ||
}) | ||
.catch(callback); | ||
}); | ||
return options; | ||
}; |
Oops, something went wrong.