Skip to content

Commit 87847e2

Browse files
authored
Merge pull request #274 from jeroen-plug/fix271
refactor(MongoBinaryDownloads): Replace decompress
2 parents 9bd61b9 + d2873ea commit 87847e2

File tree

5 files changed

+179
-217
lines changed

5 files changed

+179
-217
lines changed

.eslintrc.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,4 @@ module.exports = {
4848
jasmine: true,
4949
jest: true,
5050
},
51-
globals: {
52-
Class: true,
53-
Iterator: true,
54-
},
5551
};

packages/mongodb-memory-server-core/package.json

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
"devDependencies": {
3434
"@types/jest": "25.1.4",
3535
"@types/mongodb": "3.5.2",
36+
"@types/tar-stream": "^2.1.0",
37+
"@types/yauzl": "^2.9.1",
3638
"@typescript-eslint/eslint-plugin": "2.23.0",
3739
"@typescript-eslint/parser": "2.23.0",
3840
"cross-env": "^7.0.2",
@@ -48,7 +50,6 @@
4850
"dependencies": {
4951
"@types/cross-spawn": "^6.0.1",
5052
"@types/debug": "^4.1.5",
51-
"@types/decompress": "^4.2.3",
5253
"@types/dedent": "^0.7.0",
5354
"@types/find-cache-dir": "^3.2.0",
5455
"@types/find-package-json": "^1.1.1",
@@ -61,7 +62,6 @@
6162
"camelcase": "^5.3.1",
6263
"cross-spawn": "^7.0.1",
6364
"debug": "^4.1.1",
64-
"decompress": "^4.2.0",
6565
"dedent": "^0.7.0",
6666
"find-cache-dir": "3.3.1",
6767
"find-package-json": "^1.2.0",
@@ -70,8 +70,10 @@
7070
"lockfile": "^1.0.4",
7171
"md5-file": "^4.0.0",
7272
"mkdirp": "^1.0.3",
73-
"tmp": "^0.1.0",
74-
"uuid": "^7.0.2"
73+
"tar-stream": "^2.1.1",
74+
"uuid": "^7.0.2",
75+
"yauzl": "^2.10.0",
76+
"tmp": "^0.1.0"
7577
},
7678
"optionalDependencies": {
7779
"mongodb": "^3.5.4"

packages/mongodb-memory-server-core/src/__tests__/replset-single-restart-test.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import MongoMemoryReplSet from '../MongoMemoryReplSet';
1+
import MongoMemoryReplSet, { MongoMemoryReplSetOptsT } from '../MongoMemoryReplSet';
22
import * as tmp from 'tmp';
3+
import getPort from 'get-port';
34

45
let tmpDir: tmp.DirResult;
56
beforeEach(() => {
@@ -16,7 +17,7 @@ const sleep = (ms: number) => {
1617

1718
describe('single-member replica set', () => {
1819
it('should start multiple times', async () => {
19-
const opts: any = {
20+
const opts = {
2021
replSet: {
2122
storageEngine: 'wiredTiger',
2223
},
@@ -26,11 +27,17 @@ describe('single-member replica set', () => {
2627
dbPath: tmpDir.name,
2728
},
2829
],
29-
};
30+
} as MongoMemoryReplSetOptsT;
3031

3132
const replSetBefore = new MongoMemoryReplSet(opts);
3233
await replSetBefore.waitUntilRunning();
34+
35+
// Write real port to config (because 27017 may be busy, we need to get real port)
36+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
37+
opts.instanceOpts![0].port = await replSetBefore.servers[0].getPort();
38+
3339
await replSetBefore.stop();
40+
3441
/**
3542
* get-port has a portlocking-feature that keeps ports locked for
3643
* "a minimum of 15 seconds and a maximum of 30 seconds before being released again"

packages/mongodb-memory-server-core/src/util/MongoBinaryDownload.ts

Lines changed: 100 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,17 @@ import path from 'path';
44
import fs from 'fs';
55
import md5File from 'md5-file';
66
import https from 'https';
7-
import decompress from 'decompress'; // 💩💩💩 this package does not work with Node@11+Jest+Babel
7+
import { createUnzip } from 'zlib';
8+
import tar from 'tar-stream';
9+
import yauzl from 'yauzl';
810
import MongoBinaryDownloadUrl from './MongoBinaryDownloadUrl';
911
import { DownloadProgressT } from '../types';
1012
import { LATEST_VERSION } from './MongoBinary';
1113
import { HttpsProxyAgent } from 'https-proxy-agent';
1214
import { promisify } from 'util';
1315
import resolveConfig, { envToBool } from './resolve-config';
1416
import debug from 'debug';
17+
import dedent from 'dedent';
1518

1619
const log = debug('MongoMS:MongoBinaryDownload');
1720

@@ -186,24 +189,25 @@ export default class MongoBinaryDownload {
186189
fs.mkdirSync(extractDir);
187190
}
188191

189-
let filter;
192+
let filter: (file: string) => boolean;
190193
if (this.platform === 'win32') {
191-
filter = (file: any) => {
192-
return /bin\/mongod.exe$/.test(file.path) || /.dll$/.test(file.path);
194+
filter = (file: string) => {
195+
return /bin\/mongod.exe$/.test(file) || /.dll$/.test(file);
193196
};
194197
} else {
195-
filter = (file: any) => /bin\/mongod$/.test(file.path);
198+
filter = (file: string) => /bin\/mongod$/.test(file);
196199
}
197200

198-
await decompress(mongoDBArchive, extractDir, {
199-
// extract only `bin/mongod` file
200-
filter,
201-
// extract to root folder
202-
map: (file) => {
203-
file.path = path.basename(file.path);
204-
return file;
205-
},
206-
});
201+
if (/(.tar.gz|.tgz)$/.test(path.extname(mongoDBArchive))) {
202+
await this.extractTarGz(mongoDBArchive, extractDir, filter);
203+
} else if (/.zip$/.test(path.extname(mongoDBArchive))) {
204+
await this.extractZip(mongoDBArchive, extractDir, filter);
205+
} else {
206+
throw new Error(
207+
`MongoBinaryDownload: unsupported archive ${mongoDBArchive} (downloaded from ${this
208+
._downloadingUrl ?? 'unkown'}). Broken archive from MongoDB Provider?`
209+
);
210+
}
207211

208212
if (!(await this.locationExists(path.resolve(this.downloadDir, this.version, binaryName)))) {
209213
throw new Error(
@@ -214,6 +218,83 @@ export default class MongoBinaryDownload {
214218
return extractDir;
215219
}
216220

221+
/**
222+
* Extract a .tar.gz archive
223+
* @param mongoDBArchive Archive location
224+
* @param extractDir Directory to extract to
225+
* @param filter Method to determine which files to extract
226+
*/
227+
async extractTarGz(
228+
mongoDBArchive: string,
229+
extractDir: string,
230+
filter: (file: string) => boolean
231+
): Promise<void> {
232+
const extract = tar.extract();
233+
extract.on('entry', (header, stream, next) => {
234+
if (filter(header.name)) {
235+
stream.pipe(
236+
fs.createWriteStream(path.resolve(extractDir, path.basename(header.name)), {
237+
mode: 0o775,
238+
})
239+
);
240+
}
241+
stream.on('end', () => next());
242+
stream.resume();
243+
});
244+
245+
return new Promise((resolve, reject) => {
246+
fs.createReadStream(mongoDBArchive)
247+
.on('error', (err) => {
248+
reject('Unable to open tarball ' + mongoDBArchive + ': ' + err);
249+
})
250+
.pipe(createUnzip())
251+
.on('error', (err) => {
252+
reject('Error during unzip for ' + mongoDBArchive + ': ' + err);
253+
})
254+
.pipe(extract)
255+
.on('error', (err) => {
256+
reject('Error during untar for ' + mongoDBArchive + ': ' + err);
257+
})
258+
.on('finish', (result) => {
259+
resolve(result);
260+
});
261+
});
262+
}
263+
264+
/**
265+
* Extract a .zip archive
266+
* @param mongoDBArchive Archive location
267+
* @param extractDir Directory to extract to
268+
* @param filter Method to determine which files to extract
269+
*/
270+
async extractZip(
271+
mongoDBArchive: string,
272+
extractDir: string,
273+
filter: (file: string) => boolean
274+
): Promise<void> {
275+
return new Promise((resolve, reject) => {
276+
yauzl.open(mongoDBArchive, { lazyEntries: true }, (e, zipfile) => {
277+
if (e || !zipfile) return reject(e);
278+
zipfile.readEntry();
279+
280+
zipfile.on('end', () => resolve());
281+
282+
zipfile.on('entry', (entry) => {
283+
if (!filter(entry.fileName)) return zipfile.readEntry();
284+
zipfile.openReadStream(entry, (e, r) => {
285+
if (e || !r) return reject(e);
286+
r.on('end', () => zipfile.readEntry());
287+
r.pipe(
288+
fs.createWriteStream(path.resolve(extractDir, path.basename(entry.fileName)), {
289+
mode: 0o775,
290+
})
291+
);
292+
});
293+
});
294+
});
295+
});
296+
}
297+
217298
/**
218299
* Downlaod given httpOptions to tempDownloadLocation, then move it to downloadLocation
219300
* @param httpOptions The httpOptions directly passed to https.get
@@ -233,10 +314,10 @@ export default class MongoBinaryDownload {
233314
if (response.statusCode != 200) {
234315
if (response.statusCode === 403) {
235316
reject(
236-
new Error(
237-
"Status Code is 403 (MongoDB's 404)\n" +
238-
'This means that the requested version-platform combination dosnt exist'
239-
)
317+
new Error(dedent`
318+
Status Code is 403 (MongoDB's 404)\n
319+
This means that the requested version-platform combination dosnt exist
320+
`)
240321
);
241322
return;
242323
}
@@ -304,8 +385,7 @@ export default class MongoBinaryDownload {
304385

305386
const crReturn = this.platform === 'win32' ? '\x1b[0G' : '\r';
306387
process.stdout.write(
307-
`Downloading MongoDB ${this.version}: ${percentComplete} % (${mbComplete}mb ` +
308-
`/ ${this.dlProgress.totalMb}mb)${crReturn}`
388+
`Downloading MongoDB ${this.version}: ${percentComplete} % (${mbComplete}mb / ${this.dlProgress.totalMb}mb)${crReturn}`
309389
);
310390
}
311391

0 commit comments

Comments
 (0)