makiwara 2.1.9 โ†’ 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,5 +1,6 @@
1
1
  # makiwara
2
2
 
3
+ [![cli-available](https://badgen.net/static/cli/available/?icon=terminal)](#cli)
3
4
  [![node version](https://img.shields.io/node/v/makiwara.svg)](https://www.npmjs.com/package/makiwara)
4
5
  [![npm version](https://badge.fury.io/js/makiwara.svg)](https://badge.fury.io/js/makiwara)
5
6
  [![downloads count](https://img.shields.io/npm/dt/makiwara.svg)](https://www.npmjs.com/package/makiwara)
@@ -7,20 +8,20 @@
7
8
  [![license](https://img.shields.io/npm/l/makiwara.svg)](https://piecioshka.mit-license.org)
8
9
  [![github-ci](https://github.com/piecioshka/makiwara/actions/workflows/testing.yml/badge.svg)](https://github.com/piecioshka/makiwara/actions/workflows/testing.yml)
9
10
 
10
- ๐Ÿ”จ Benchmark URL to gain HTTP requests limits
11
+ ๐Ÿ”จ CLI to benchmark URL to gain HTTP requests limits
11
12
 
12
- ## Install
13
+ ## Usage
14
+
15
+ Installation:
13
16
 
14
17
  ```bash
15
- npm install -g makiwara
18
+ npm install makiwara
16
19
  ```
17
20
 
18
- ## Usage
19
-
20
21
  ```javascript
21
- const { attack } = require('makiwara');
22
+ const { benchmark } = require('makiwara');
22
23
 
23
- attack('https://example.org', [1, 5, 10], 'sequence')
24
+ benchmark('https://example.org', [1, 5, 10], 'sequence')
24
25
  .then((result) => {
25
26
  console.log(result);
26
27
  })
@@ -31,9 +32,17 @@ attack('https://example.org', [1, 5, 10], 'sequence')
31
32
 
32
33
  ## CLI
33
34
 
34
- ```text
35
+ Installation:
36
+
37
+ ```bash
38
+ npm install -g makiwara
39
+ ```
40
+
41
+ ```bash
35
42
  makiwara --help
43
+ ```
36
44
 
45
+ ```text
37
46
  Usage: cli [options]
38
47
 
39
48
  Example:
@@ -41,7 +50,7 @@ Example:
41
50
 
42
51
  Options:
43
52
  -V, --version output the version number
44
- -u, --url <url> Define URL to attack. Ex. https://example.org/
53
+ -u, --url <url> Define URL to benchmark. Ex. https://example.org/
45
54
  -t, --timelimit [numbers] Define list of time thresholds (in seconds). Ex. 10,100,1000
46
55
  -s, --strategy <concurrent|sequence> Define strategy for making requests
47
56
  -h, --help output usage information
@@ -88,18 +97,6 @@ Result:
88
97
  โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•งโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
89
98
  ```
90
99
 
91
- ## Unit tests
92
-
93
- ```bash
94
- npm test
95
- ```
96
-
97
- ## Code coverage
98
-
99
- ```bash
100
- npm run coverage
101
- ```
102
-
103
100
  ## License
104
101
 
105
- [The MIT License](https://piecioshka.mit-license.org) @ 2017-2019
102
+ [The MIT License](https://piecioshka.mit-license.org) @ 2017
package/bin/cli.js CHANGED
@@ -1,98 +1,112 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const fs = require('fs');
4
- const path = require('path');
5
-
6
- const http = require('http');
7
- const https = require('https');
3
+ const http = require("http");
4
+ const https = require("https");
8
5
  http.globalAgent.maxSockets = https.globalAgent.maxSockets = 512;
9
6
  // http.globalAgent.maxFreeSockets = https.globalAgent.maxFreeSockets = 512;
10
7
 
11
- const program = require('commander');
12
- const ora = require('ora');
13
- const isUrl = require('is-url');
14
- const bold = require('ansi-bold');
8
+ const { Command } = require("commander");
9
+ const program = new Command();
10
+
11
+ const ora = require("ora");
12
+ const isUrl = require("is-url");
13
+ const bold = require("ansi-bold");
15
14
 
16
- const { attack } = require('../index');
17
- const HTTP_STATUS = require('../src/http-status-codes');
18
- const { displaySummary, displayError } = require('../src/display');
19
- const { makeRequest } = require('../src/make-requests');
15
+ const logger = require("../src/color-logs");
16
+ const { benchmark } = require("../index");
17
+ const HTTP_STATUS = require("../src/http-status-codes");
18
+ const { displaySummary } = require("../src/display");
19
+ const { makeRequest } = require("../src/make-requests");
20
20
 
21
- // eslint-disable-next-line no-sync
22
- const pkg = JSON.parse(fs.readFileSync(
23
- path.join(__dirname, '..', 'package.json')
24
- ).toString());
21
+ const pkg = require("../package.json");
25
22
  const STRATEGY_REGEXP = /^(concurrent|sequence)$/;
26
23
 
27
24
  program
28
25
  .version(pkg.version)
29
- .option('-u, --url <url>', 'Define URL to attack. Ex. https://example.org/')
30
- .option('-t, --timelimit [numbers]', 'Define list of time thresholds (in seconds). Ex. 10,100,1000')
31
- .option('-s, --strategy <concurrent|sequence>', 'Define strategy for making requests')
32
- .description('Example:\n\tmakiwara -u https://localhost:3000 -t 10 -s sequence')
26
+ .option(
27
+ "-u, --url <url>",
28
+ "Define URL to benchmark. Ex. https://example.org/"
29
+ )
30
+ .option(
31
+ "-t, --timelimit [numbers]",
32
+ "Define list of time thresholds (in seconds). Ex. 10,100,1000"
33
+ )
34
+ .option(
35
+ "-s, --strategy <concurrent|sequence>",
36
+ "Define strategy for making requests"
37
+ )
38
+ .description(
39
+ "Example:\n\tmakiwara -u https://localhost:3000 -t 10 -s sequence"
40
+ )
33
41
  .parse(process.argv);
34
42
 
35
- if (typeof program.url !== 'string') {
36
- console.red('Error: url is not a string');
43
+ const options = program.opts();
44
+
45
+ if (typeof options.url !== "string") {
46
+ displayHeader();
47
+ logger.red("Error: url is not a string\n");
37
48
  program.help();
38
49
  }
39
50
 
40
- if (!isUrl(program.url)) {
41
- console.red('Error: url is not correct format');
51
+ if (!isUrl(options.url)) {
52
+ displayHeader();
53
+ logger.red("Error: url is not correct format\n");
42
54
  program.help();
43
55
  }
44
56
 
45
- if (!program.timelimit) {
46
- program.timelimit = '1,3,5';
47
- console.yellow('Ups... you did not put "timelimit" of thresholds');
48
- console.yellow(`Thresholds are sets to: ${bold(program.timelimit)} (seconds)\n`);
57
+ if (!options.timelimit) {
58
+ options.timelimit = "1,3,5";
59
+ logger.yellow('Ups... you did not define "timelimit" of thresholds');
60
+ logger.yellow(
61
+ `Thresholds are sets to: ${bold(options.timelimit)} (seconds)\n`
62
+ );
49
63
  }
50
64
 
51
- if (!(STRATEGY_REGEXP).test(program.strategy)) {
52
- program.strategy = 'concurrent';
53
- console.yellow('Ups... you did not put "strategy"');
54
- console.yellow(`Default strategy is: ${program.strategy}\n`);
65
+ if (!STRATEGY_REGEXP.test(options.strategy)) {
66
+ options.strategy = "concurrent";
67
+ logger.yellow('Ups... you did not define "strategy"');
68
+ logger.yellow(`Default strategy is: ${options.strategy}\n`);
55
69
  }
56
70
 
57
- const url = program.url;
58
- const timeLimit = program.timelimit.split(',').map(Number);
59
- const strategy = program.strategy;
71
+ const url = options.url;
72
+ const timeLimit = options.timelimit.split(",").map(Number);
73
+ const strategy = options.strategy;
60
74
  let spinner = null;
61
75
 
62
76
  function displayDelimiter() {
63
- console.gray('----------------------------------------------------\n');
77
+ logger.gray("----------------------------------------------------\n");
64
78
  }
65
79
 
66
80
  function displayHeader() {
67
- console.log(`${pkg.name}, Version ${pkg.version}`);
68
- const currentYear = new Date().getFullYear();
69
- console.log(`Copyright 2017-${currentYear} ${pkg.author.name} <${pkg.author.email}> ${pkg.author.url}`);
70
- console.log(`The ${pkg.license} License, https://piecioshka.mit-license.org/\n`);
71
- console.log(`> ${pkg.description}\n`);
81
+ const author = `${pkg.author.name} <${pkg.author.email}> ${pkg.author.url}`;
82
+ console.log(`${pkg.name} v${pkg.version}`);
83
+ console.log(`Copyright (c) ${new Date().getFullYear()} ${author}\n`);
72
84
  }
73
85
 
74
86
  async function sendTestRequest(testUrl) {
75
87
  spinner.succeed(`Start testing... ${bold(testUrl)}`);
76
88
  const response = await makeRequest(testUrl, { agent: false });
77
89
  if (response.status !== HTTP_STATUS.OK) {
78
- console.red(`HTTP Status Code: ${bold(response.status)}`);
79
- console.yellow(`Response Body: ${bold(response.text)}`);
90
+ logger.red(`HTTP Status Code: ${bold(response.status)}`);
91
+ logger.yellow(`Response Body: ${bold(response.text)}`);
80
92
  }
81
- spinner.succeed(`Testing completed (response: ${bold(response.text.length)} Bytes)`);
93
+ spinner.succeed(
94
+ `Testing completed (response: ${bold(response.text.length)} Bytes)`
95
+ );
82
96
  }
83
97
 
84
98
  async function main() {
85
99
  displayHeader();
86
100
 
87
- spinner = ora('Loading').start();
101
+ spinner = ora("Loading").start();
88
102
 
89
103
  try {
90
104
  await sendTestRequest(url);
91
105
 
92
- spinner.succeed('Start attacking...');
93
- const results = await attack(url, timeLimit, strategy);
106
+ spinner.succeed("Start benchmarking...");
107
+ const results = await benchmark(url, timeLimit, strategy);
94
108
  spinner.stop();
95
- spinner.succeed('Attacking completed\n');
109
+ spinner.succeed("Benchmarking completed\n");
96
110
 
97
111
  results.forEach((result, index) => {
98
112
  displaySummary(result);
@@ -103,7 +117,7 @@ async function main() {
103
117
  });
104
118
  } catch (err) {
105
119
  spinner.stop();
106
- displayError(err);
120
+ logger.red(err);
107
121
  }
108
122
 
109
123
  // eslint-disable-next-line no-process-exit
package/index.js CHANGED
@@ -1,22 +1 @@
1
- const { makeRequestsInConcurrentMode, makeRequestsInSequenceMode } = require('./src/make-requests');
2
-
3
- require('./src/color-logs');
4
-
5
- const strategies = new Map();
6
- strategies.set('sequence', makeRequestsInSequenceMode);
7
- strategies.set('concurrent', makeRequestsInConcurrentMode);
8
-
9
- function attack(url, timeLimits, strategy) {
10
- const method = strategies.get(strategy);
11
-
12
- return Promise.all(
13
- timeLimits
14
- // Remove zeros timeLimits
15
- .filter((k) => k)
16
- .map((duration) => method(url, duration))
17
- );
18
- }
19
-
20
- module.exports = {
21
- attack
22
- };
1
+ module.exports = require("./src/benchmark");
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "makiwara",
3
- "description": "๐Ÿ”จ Benchmark URL to gain HTTP requests limits",
4
- "version": "2.1.9",
3
+ "description": "๐Ÿ”จ CLI to benchmark URL to gain HTTP requests limits",
4
+ "version": "2.2.1",
5
5
  "license": "MIT",
6
6
  "author": {
7
7
  "name": "Piotr Kowalski",
@@ -11,43 +11,45 @@
11
11
  "scripts": {
12
12
  "clear": "rm -rf dist/ coverage/ .nyc_output/",
13
13
  "clear:all": "rm -rf node_modules/ && npm run clear",
14
- "test": "jasmine test/specs/*.js",
15
- "coverage": "nyc npm run test && nyc report --reporter=html",
14
+ "test": "jest --watchAll=false",
15
+ "test:watch": "jest",
16
+ "coverage": "jest --watchAll=false --coverage",
16
17
  "lint": "eslint src/",
17
18
  "snyk-protect": "snyk protect",
18
19
  "prepare": "npm run snyk-protect"
19
20
  },
20
21
  "dependencies": {
21
- "@types/node": "^22.7.5",
22
+ "@types/jest": "^29.5.14",
22
23
  "ansi-bold": "^0.1.1",
23
- "ansi-cyan": "^0.1.1",
24
24
  "ansi-gray": "^0.1.1",
25
25
  "ansi-red": "^0.1.1",
26
26
  "ansi-yellow": "^0.1.1",
27
- "axios": "^1.7.7",
28
- "commander": "^12.1.0",
27
+ "axios": "^1.7.9",
29
28
  "http-status-codes": "^2.3.0",
30
29
  "is-url": "^1.2.4",
30
+ "jest": "^29.7.0",
31
31
  "node-fetch": "^3.3.2",
32
- "ora": "^8.1.0",
33
- "snyk": "^1.1293.1",
34
- "table": "^6.8.2"
32
+ "snyk": "^1.1295.0",
33
+ "table": "^6.9.0"
35
34
  },
36
35
  "devDependencies": {
37
- "@types/jasmine": "^5.1.4",
36
+ "@types/node": "^22.10.7",
37
+ "commander": "^13.0.0",
38
38
  "eslint": "^8.6.0",
39
- "eslint-config-piecioshka": "^2.3.1",
40
- "jasmine": "^5.4.0",
41
- "nock": "^13.5.5",
42
- "nyc": "^15.1.0"
39
+ "eslint-config-piecioshka": "^2.3.6",
40
+ "nock": "^13.5.6",
41
+ "ora": "^5.4.1"
43
42
  },
44
43
  "repository": {
45
44
  "type": "git",
46
- "url": "git+https://github.com/piecioshka/makiwara.git"
45
+ "url": "git+ssh://git@github.com/piecioshka/makiwara.git"
47
46
  },
48
47
  "bugs": {
49
48
  "url": "https://github.com/piecioshka/makiwara/issues"
50
49
  },
50
+ "engines": {
51
+ "node": ">=14"
52
+ },
51
53
  "files": [
52
54
  "bin",
53
55
  "src",
@@ -56,9 +58,8 @@
56
58
  "README.md"
57
59
  ],
58
60
  "keywords": [
59
- "app",
60
- "test",
61
- "attack",
61
+ "cli",
62
+ "benchmark",
62
63
  "multi",
63
64
  "statistics",
64
65
  "summary",
@@ -66,9 +67,7 @@
66
67
  "verify",
67
68
  "request",
68
69
  "time",
69
- "analyze",
70
- "cli",
71
- "commonjs"
70
+ "analyze"
72
71
  ],
73
72
  "main": "./index.js",
74
73
  "bin": {
@@ -0,0 +1,23 @@
1
+ const {
2
+ makeRequestsInConcurrentMode,
3
+ makeRequestsInSequenceMode,
4
+ } = require("./make-requests");
5
+
6
+ const strategies = new Map();
7
+ strategies.set("sequence", makeRequestsInSequenceMode);
8
+ strategies.set("concurrent", makeRequestsInConcurrentMode);
9
+
10
+ function benchmark(url, timeLimits, strategy) {
11
+ const method = strategies.get(strategy);
12
+
13
+ return Promise.all(
14
+ timeLimits
15
+ // Remove zeros timeLimits
16
+ .filter((k) => k)
17
+ .map((duration) => method(url, duration))
18
+ );
19
+ }
20
+
21
+ module.exports = {
22
+ benchmark,
23
+ };
@@ -0,0 +1,15 @@
1
+ const nock = require("nock");
2
+ const HTTP_STATUS = require("./http-status-codes");
3
+ const { benchmark } = require("./benchmark");
4
+
5
+ describe("Benchmark", () => {
6
+ it("benchmark should send request", async () => {
7
+ const targetUrl = "https://localhost/";
8
+ nock(targetUrl).persist().get("/").reply(204, []);
9
+
10
+ const responses = await benchmark(targetUrl, [1], "sequence");
11
+ const requests = responses[0].requests;
12
+ const status = requests[0].status;
13
+ expect(status).toEqual(HTTP_STATUS.NO_CONTENT);
14
+ });
15
+ });
package/src/color-logs.js CHANGED
@@ -1,17 +1,10 @@
1
1
  function setupColorLogFunction(name) {
2
2
  const fn = require(`ansi-${name}`);
3
- console[name] = (...args) => console.log(...args.map(fn));
3
+ return (...args) => console.log(...args.map(fn));
4
4
  }
5
5
 
6
- setupColorLogFunction("red");
7
- setupColorLogFunction("yellow");
8
- setupColorLogFunction("gray");
9
- setupColorLogFunction("cyan");
10
-
11
- /**
12
- * @type console
13
- * @property red
14
- * @property yellow
15
- * @property gray
16
- * @property cyan
17
- */
6
+ module.exports = {
7
+ red: setupColorLogFunction("red"),
8
+ yellow: setupColorLogFunction("yellow"),
9
+ gray: setupColorLogFunction("gray"),
10
+ };
package/src/display.js CHANGED
@@ -2,7 +2,7 @@ const { table } = require("table");
2
2
  const HTTPStatusCodes = require("http-status-codes");
3
3
  const bold = require("ansi-bold");
4
4
 
5
- const { collapseArray } = require("./object-util");
5
+ const logger = require("../src/color-logs");
6
6
 
7
7
  const SECOND_IN_MILLISECONDS = 1000;
8
8
 
@@ -13,60 +13,50 @@ const tableOptions = {
13
13
  },
14
14
  };
15
15
 
16
- function appendHttpStatusCodeLabel(statusCodeEntries) {
17
- statusCodeEntries.forEach((entry) => {
18
- let label = null;
19
- try {
20
- label = HTTPStatusCodes.getStatusText(entry[0]);
21
- } catch (err) {
22
- console.red(err);
23
- }
24
- if (typeof label === "string") {
25
- entry[0] = `${entry[0]} ${label}`;
26
- }
27
- });
28
- }
16
+ function displayRequestsSummary(results) {
17
+ const aggregateResults = results.requests.reduce((acc, item) => {
18
+ acc[item.status] = acc[item.status] ? acc[item.status] + 1 : 1;
19
+ return acc;
20
+ }, {});
29
21
 
30
- function displayRequestsSummary(attackResults) {
31
- const statusCodes = collapseArray(
32
- attackResults.requests.map((r) => r.status)
33
- );
34
- const isEmptyResults = statusCodes.length === 0;
22
+ const codes = Object.keys(aggregateResults).map(Number);
35
23
 
36
- if (isEmptyResults) {
37
- statusCodes["-"] = -1;
38
- } else {
39
- appendHttpStatusCodeLabel(statusCodes);
24
+ if (codes.length === 0) {
25
+ logger.error("No requests were made");
26
+ return;
40
27
  }
41
28
 
42
- const data = [["HTTP Status Code", "Requests quantity"].map(bold)].concat(
43
- statusCodes
44
- );
29
+ const statuses = codes.reduce((acc, code) => {
30
+ const label = HTTPStatusCodes.getReasonPhrase(code);
31
+ acc[`${code} ${label}`] = aggregateResults[code];
32
+ return acc;
33
+ }, {});
34
+
35
+ const data = [
36
+ ["HTTP Status Code", "Requests quantity"].map(bold),
37
+ ...Object.entries(statuses),
38
+ ];
39
+
45
40
  console.log(table(data, tableOptions));
46
41
  }
47
42
 
48
- function displayAttackSummary(results) {
43
+ function displayBenchmarkSummary(results) {
49
44
  const meta = [];
50
- meta.push(["Type", results.type]);
45
+ meta.push([bold("Type"), results.type]);
51
46
  const durationInSeconds = results.duration / SECOND_IN_MILLISECONDS;
52
47
  meta.push([
53
- "Effective Duration",
48
+ bold("Effective Duration"),
54
49
  `${durationInSeconds.toLocaleString()} seconds`,
55
50
  ]);
56
- meta.push(["Times", `${results.times}`]);
51
+ meta.push([bold("Times"), results.times]);
57
52
  console.log(table(meta, tableOptions));
58
53
  }
59
54
 
60
- function displaySummary(attackResults) {
61
- displayRequestsSummary(attackResults);
62
- displayAttackSummary(attackResults);
63
- }
64
-
65
- function displayError(err) {
66
- console.log(`${err.name}: ${err.message}\n`);
55
+ function displaySummary(results) {
56
+ displayRequestsSummary(results);
57
+ displayBenchmarkSummary(results);
67
58
  }
68
59
 
69
60
  module.exports = {
70
61
  displaySummary,
71
- displayError,
72
62
  };
@@ -18,8 +18,8 @@ async function makeRequest(url, options = {}) {
18
18
  const protocol = getProtocol(url);
19
19
  return new Promise((resolve, reject) => {
20
20
  protocol.get(url, options, (res) => {
21
- res.addListener("data", (data) => {
22
- response.text += data.toString();
21
+ res.addListener("data", (buffer) => {
22
+ response.text += buffer.toString();
23
23
  });
24
24
  res.addListener("error", (err) => {
25
25
  reject(err);
@@ -0,0 +1,41 @@
1
+ const nock = require("nock");
2
+ const {
3
+ makeRequest,
4
+ makeRequestsInConcurrentMode,
5
+ makeRequestsInSequenceMode,
6
+ } = require("./make-requests");
7
+ const HTTP_STATUS = require("./http-status-codes");
8
+
9
+ describe("makeRequests", () => {
10
+ const targetUrl = "https://localhost/";
11
+
12
+ beforeEach(() => {
13
+ nock(targetUrl)
14
+ .persist()
15
+ .get("/")
16
+ .delay(100)
17
+ .times(Infinity)
18
+ .reply(204, []);
19
+ });
20
+
21
+ it("should returns status code", async () => {
22
+ const response = await makeRequest(targetUrl, { agent: false });
23
+ expect(response.status).toEqual(HTTP_STATUS.NO_CONTENT);
24
+ });
25
+
26
+ it.skip("should make HTTP requests in concurrent mode", async (done) => {
27
+ const results = await makeRequestsInConcurrentMode(targetUrl, 1);
28
+ setTimeout(() => {
29
+ expect(results.requests.length).toBeGreaterThan(10);
30
+ done();
31
+ }, 100);
32
+ });
33
+
34
+ it.skip("should make HTTP requests in sequence mode", async (done) => {
35
+ const results = await makeRequestsInSequenceMode(targetUrl, 1);
36
+ setTimeout(() => {
37
+ expect(results.requests.length).toBeLessThan(11);
38
+ done();
39
+ }, 100);
40
+ });
41
+ });
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- The MIT License (MIT)
2
- Copyright (c) 2017 Piotr Kowalski
3
-
4
- Permission is hereby granted, free of charge, to any person obtaining a copy
5
- of this software and associated documentation files (the "Software"), to deal
6
- in the Software without restriction, including without limitation the rights
7
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
- copies of the Software, and to permit persons to whom the Software is
9
- furnished to do so, subject to the following conditions:
10
-
11
- The above copyright notice and this permission notice shall be included in all
12
- copies or substantial portions of the Software.
13
-
14
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18
- DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19
- OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
20
- OR OTHER DEALINGS IN THE SOFTWARE.
21
-
@@ -1,17 +0,0 @@
1
- function collapseArray(array) {
2
- const hashMap = array.reduce((mem, item) => {
3
- if (!mem[item]) {
4
- mem[item] = 0;
5
- }
6
- mem[item]++;
7
- return mem;
8
- }, {});
9
-
10
- const entries = Object.entries(hashMap);
11
-
12
- return entries.map((entry) => Number(entry[0]));
13
- }
14
-
15
- module.exports = {
16
- collapseArray,
17
- };