diff --git a/package-lock.json b/package-lock.json index 942b7226..03dea461 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@neuralegion/nexploit-cli", - "version": "2.3.0", + "version": "2.4.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 7eebb3a8..28bd2147 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@neuralegion/nexploit-cli", - "version": "2.3.0", + "version": "2.4.0", "private": false, "repository": { "type": "git", diff --git a/src/Commands/GenerateArchive.ts b/src/Commands/GenerateArchive.ts index 14601553..6a60b86f 100644 --- a/src/Commands/GenerateArchive.ts +++ b/src/Commands/GenerateArchive.ts @@ -1,5 +1,8 @@ import { writeFile as writeFileCb } from 'fs'; -import { MockRequest, NexMockToRequestsParser } from '../Parsers/NexMockToRequestsParser'; +import { + MockRequest, + NexMockToRequestsParser +} from '../Parsers/NexMockToRequestsParser'; import { RequestCrawler } from '../Parsers/RequestCrawler'; import * as yargs from 'yargs'; import { InlineHeaders } from '../Parsers/InlineHeaders'; @@ -7,6 +10,8 @@ import { basename } from 'path'; import { NexMockFileParser } from '../Parsers/NexMockFileParser'; import { Options } from 'request'; import { promisify } from 'util'; +import { split } from '../Utils/split'; +import { generatorFileNameFactory } from '../Utils/generateFileName'; const writeFile = promisify(writeFileCb); @@ -34,6 +39,13 @@ export class GenerateArchive implements yargs.CommandModule { describe: 'Time to wait for a server to send response headers (and start the response body) before aborting the request.' }) + .option('split', { + alias: 's', + number: true, + default: 1, + describe: + 'Number of the HAR chunks. Allows to split a mock file to into multiple HAR files.' + }) .option('archive', { alias: 'f', normalize: true, @@ -57,32 +69,57 @@ export class GenerateArchive implements yargs.CommandModule { public async handler(args: yargs.Arguments): Promise { try { const nexMockFileParser: NexMockFileParser = new NexMockFileParser(); + + const nexMocks: MockRequest[] = await nexMockFileParser.parse( + args.mockfile as string + ); + console.log( + `${basename(args.mockfile as string)} was verified and parsed.` + ); const nexMockRequestsParser: NexMockToRequestsParser = new NexMockToRequestsParser( { url: args.target as string, headers: new InlineHeaders(args.header as string[]).get() } ); - const crawler: RequestCrawler = new RequestCrawler({ - timeout: args.timeout as number, - pool: args.pool as number - }); - const nexMocks: MockRequest[] = await nexMockFileParser.parse( - args.mockfile as string - ); - console.log(`${basename(args.mockfile as string)} was verified and parsed.`); const requestOptions: Options[] = await nexMockRequestsParser.parse( nexMocks ); console.log(`${requestOptions.length} requests were prepared.`); - const harFile: string = await crawler.parse(requestOptions); - await writeFile(args.archive as string, harFile, { encoding: 'utf8' }); + const chunks: Options[][] = + (args.split as number) > 0 + ? split( + requestOptions, + requestOptions.length / (args.split as number) + ) + : [requestOptions]; + + const generateFileName: ( + filePath: string + ) => string = generatorFileNameFactory(); + + const fileNames: string[] = await Promise.all( + chunks.map(async (items: Options[]) => { + const crawler: RequestCrawler = new RequestCrawler({ + timeout: args.timeout as number, + pool: args.pool as number + }); + const harFile: string = await crawler.parse(items); + const fileName: string = generateFileName(args.archive as string); + await writeFile(fileName, harFile, { + encoding: 'utf8' + }); + return fileName; + }) + ); + + const plural: boolean = fileNames.length > 1; console.log( - `${basename( - args.archive as string - )} archive was created on base ${basename( + `${fileNames.map((name: string) => basename(name))} ${ + plural ? 'archives' : 'archive' + } ${plural ? 'were' : 'was'} created on base ${basename( args.mockfile as string )} mockfile.` ); diff --git a/src/Parsers/RequestCrawler.ts b/src/Parsers/RequestCrawler.ts index ebc4ef50..f64af636 100644 --- a/src/Parsers/RequestCrawler.ts +++ b/src/Parsers/RequestCrawler.ts @@ -2,6 +2,7 @@ import { CaptureHar } from 'capture-har'; import * as request from 'request'; import { CoreOptions, Options } from 'request'; +import {split} from '../Utils/split'; export class RequestCrawler { private readonly options: CoreOptions; @@ -28,7 +29,7 @@ export class RequestCrawler { public async parse(data: Options | Options[]): Promise { const requests: Options[] = Array.isArray(data) ? data : [data]; - const chunks: Options[][] = this.splitByChunks( + const chunks: Options[][] = split( requests, this.pool ); @@ -55,19 +56,4 @@ export class RequestCrawler { .once('error', (err: Error) => resolve()) ); } - - private splitByChunks(array: T, count: number): R[][] { - if (!Array.isArray(array)) { - throw new TypeError(`First argument must be an instance of Array.`); - } - - const countItemInChunk: number = Math.ceil(array.length / count); - - return Array(countItemInChunk) - .fill(null) - .map( - (_value: string, i: number) => - array.slice(i * count, i * count + count) as R[] - ); - } } diff --git a/src/Utils/generateFileName.ts b/src/Utils/generateFileName.ts new file mode 100644 index 00000000..2482754a --- /dev/null +++ b/src/Utils/generateFileName.ts @@ -0,0 +1,20 @@ +import { basename, extname, resolve } from 'path'; + +export const generatorFileNameFactory: () => ( + filePath: string +) => string = () => { + let counter: number = 1; + return (filePath: string) => { + const ext: string = extname(filePath); + const name: string = basename(filePath, ext); + if (counter === 1) { + counter++; + return filePath; + } + return resolve(filePath, '..', `${name}_${counter++}${ext}`); + }; +}; + +export const globalFileNameGenerator: ( + filePath: string +) => string = generatorFileNameFactory(); diff --git a/src/Utils/split.ts b/src/Utils/split.ts new file mode 100644 index 00000000..4485d92d --- /dev/null +++ b/src/Utils/split.ts @@ -0,0 +1,14 @@ +export function split(array: T, count: number): R[][] { + if (!Array.isArray(array)) { + throw new TypeError(`First argument must be an instance of Array.`); + } + + const countItemInChunk: number = Math.ceil(array.length / count); + + return Array(countItemInChunk) + .fill(null) + .map( + (_value: string, i: number) => + array.slice(i * count, i * count + count) as R[] + ); +}