@govtechsg/oobee 0.10.39 → 0.10.43

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/src/utils.ts CHANGED
@@ -2,29 +2,31 @@ import { execSync, spawnSync } from 'child_process';
2
2
  import path from 'path';
3
3
  import os from 'os';
4
4
  import fs from 'fs-extra';
5
+ import axe, { Rule } from 'axe-core';
5
6
  import constants, {
6
7
  BrowserTypes,
7
8
  destinationPath,
8
9
  getIntermediateScreenshotsPath,
9
10
  } from './constants/constants.js';
10
- import { silentLogger } from './logs.js';
11
+ import { consoleLogger, silentLogger } from './logs.js';
12
+ import { getAxeConfiguration } from './crawlers/custom/getAxeConfiguration.js';
11
13
 
12
14
  export const getVersion = () => {
13
- const loadJSON = filePath =>
15
+ const loadJSON = (filePath: string): { version: string } =>
14
16
  JSON.parse(fs.readFileSync(new URL(filePath, import.meta.url)).toString());
15
17
  const versionNum = loadJSON('../package.json').version;
16
18
 
17
19
  return versionNum;
18
20
  };
19
21
 
20
- export const getHost = url => new URL(url).host;
22
+ export const getHost = (url: string): string => new URL(url).host;
21
23
 
22
24
  export const getCurrentDate = () => {
23
25
  const date = new Date();
24
26
  return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`;
25
27
  };
26
28
 
27
- export const isWhitelistedContentType = contentType => {
29
+ export const isWhitelistedContentType = (contentType: string): boolean => {
28
30
  const whitelist = ['text/html'];
29
31
  return whitelist.filter(type => contentType.trim().startsWith(type)).length === 1;
30
32
  };
@@ -42,7 +44,7 @@ export const getStoragePath = (randomToken: string): string => {
42
44
  return `${constants.exportDirectory}/${randomToken}`;
43
45
  };
44
46
 
45
- export const createDetailsAndLogs = async randomToken => {
47
+ export const createDetailsAndLogs = async (randomToken: string): Promise<void> => {
46
48
  const storagePath = getStoragePath(randomToken);
47
49
  const logPath = `logs/${randomToken}`;
48
50
  try {
@@ -56,20 +58,24 @@ export const createDetailsAndLogs = async randomToken => {
56
58
  await fs.copy('errors.txt', `${logPath}/${randomToken}.txt`);
57
59
  } catch (error) {
58
60
  if (error.code === 'EBUSY') {
59
- console.log(
61
+ consoleLogger.error(
60
62
  `Unable to copy the file from 'errors.txt' to '${logPath}/${randomToken}.txt' because it is currently in use.`,
61
63
  );
62
- console.log(
64
+ consoleLogger.error(
63
65
  'Please close any applications that might be using this file and try again.',
64
66
  );
65
67
  } else {
66
- console.log(`An unexpected error occurred while copying the file: ${error.message}`);
68
+ consoleLogger.error(
69
+ `An unexpected error occurred while copying the file: ${error.message}`,
70
+ );
67
71
  }
68
72
  }
69
73
  }
70
74
  });
71
75
  } catch (error) {
72
- console.log(`An error occurred while setting up storage or log directories: ${error.message}`);
76
+ consoleLogger.error(
77
+ `An error occurred while setting up storage or log directories: ${error.message}`,
78
+ );
73
79
  }
74
80
  };
75
81
 
@@ -96,7 +102,7 @@ export const getUserDataTxt = () => {
96
102
  return null;
97
103
  };
98
104
 
99
- export const writeToUserDataTxt = async (key, value) => {
105
+ export const writeToUserDataTxt = async (key: string, value: string): Promise<void> => {
100
106
  const textFilePath = getUserDataFilePath();
101
107
 
102
108
  // Create file if it doesn't exist
@@ -113,25 +119,27 @@ export const writeToUserDataTxt = async (key, value) => {
113
119
  }
114
120
  };
115
121
 
116
- export const createAndUpdateResultsFolders = async randomToken => {
122
+ export const createAndUpdateResultsFolders = async (randomToken: string): Promise<void> => {
117
123
  const storagePath = getStoragePath(randomToken);
118
124
  await fs.ensureDir(`${storagePath}`);
119
125
 
120
126
  const intermediatePdfResultsPath = `${randomToken}/${constants.pdfScanResultFileName}`;
121
127
 
122
- const transferResults = async (intermPath, resultFile) => {
128
+ const transferResults = async (intermPath: string, resultFile: string): Promise<void> => {
123
129
  try {
124
130
  if (fs.existsSync(intermPath)) {
125
131
  await fs.copy(intermPath, `${storagePath}/${resultFile}`);
126
132
  }
127
133
  } catch (error) {
128
134
  if (error.code === 'EBUSY') {
129
- console.log(
135
+ consoleLogger.error(
130
136
  `Unable to copy the file from ${intermPath} to ${storagePath}/${resultFile} because it is currently in use.`,
131
137
  );
132
- console.log('Please close any applications that might be using this file and try again.');
138
+ consoleLogger.error(
139
+ 'Please close any applications that might be using this file and try again.',
140
+ );
133
141
  } else {
134
- console.log(
142
+ consoleLogger.error(
135
143
  `An unexpected error occurred while copying the file from ${intermPath} to ${storagePath}/${resultFile}: ${error.message}`,
136
144
  );
137
145
  }
@@ -141,20 +149,20 @@ export const createAndUpdateResultsFolders = async randomToken => {
141
149
  await Promise.all([transferResults(intermediatePdfResultsPath, constants.pdfScanResultFileName)]);
142
150
  };
143
151
 
144
- export const createScreenshotsFolder = randomToken => {
152
+ export const createScreenshotsFolder = (randomToken: string): void => {
145
153
  const storagePath = getStoragePath(randomToken);
146
154
  const intermediateScreenshotsPath = getIntermediateScreenshotsPath(randomToken);
147
155
  if (fs.existsSync(intermediateScreenshotsPath)) {
148
156
  fs.readdir(intermediateScreenshotsPath, (err, files) => {
149
157
  if (err) {
150
- console.log(`Screenshots were not moved successfully: ${err.message}`);
158
+ consoleLogger.error(`Screenshots were not moved successfully: ${err.message}`);
151
159
  }
152
160
 
153
161
  if (!fs.existsSync(destinationPath(storagePath))) {
154
162
  try {
155
163
  fs.mkdirSync(destinationPath(storagePath), { recursive: true });
156
164
  } catch (error) {
157
- console.error('Screenshots folder was not created successfully:', error);
165
+ consoleLogger.error('Screenshots folder was not created successfully:', error);
158
166
  }
159
167
  }
160
168
 
@@ -167,32 +175,20 @@ export const createScreenshotsFolder = randomToken => {
167
175
 
168
176
  fs.rmdir(intermediateScreenshotsPath, rmdirErr => {
169
177
  if (rmdirErr) {
170
- console.log(rmdirErr);
178
+ consoleLogger.error(rmdirErr);
171
179
  }
172
180
  });
173
181
  });
174
182
  }
175
183
  };
176
184
 
177
- export const cleanUp = async pathToDelete => {
185
+ export const cleanUp = (pathToDelete: string): void => {
178
186
  fs.removeSync(pathToDelete);
179
187
  };
180
188
 
181
- /* istanbul ignore next */
182
- // export const getFormattedTime = () =>
183
- // new Date().toLocaleTimeString('en-GB', {
184
- // year: 'numeric',
185
- // month: 'short',
186
- // day: 'numeric',
187
- // hour12: true,
188
- // hour: 'numeric',
189
- // minute: '2-digit',
190
- // timeZoneName: "longGeneric",
191
- // });
192
-
193
189
  export const getWcagPassPercentage = (
194
190
  wcagViolations: string[],
195
- showEnableWcagAaa: boolean
191
+ showEnableWcagAaa: boolean,
196
192
  ): {
197
193
  passPercentageAA: string;
198
194
  totalWcagChecksAA: number;
@@ -201,20 +197,28 @@ export const getWcagPassPercentage = (
201
197
  totalWcagChecksAAandAAA: number;
202
198
  totalWcagViolationsAAandAAA: number;
203
199
  } => {
204
-
205
200
  // These AAA rules should not be counted as WCAG Pass Percentage only contains A and AA
206
201
  const wcagAAALinks = ['WCAG 1.4.6', 'WCAG 2.2.4', 'WCAG 2.4.9', 'WCAG 3.1.5', 'WCAG 3.2.5'];
207
202
  const wcagAAA = ['wcag146', 'wcag224', 'wcag249', 'wcag315', 'wcag325'];
208
-
203
+
209
204
  const wcagLinksAAandAAA = constants.wcagLinks;
210
-
205
+
211
206
  const wcagViolationsAAandAAA = showEnableWcagAaa ? wcagViolations.length : null;
212
207
  const totalChecksAAandAAA = showEnableWcagAaa ? Object.keys(wcagLinksAAandAAA).length : null;
213
- const passedChecksAAandAAA = showEnableWcagAaa ? totalChecksAAandAAA - wcagViolationsAAandAAA : null;
214
- const passPercentageAAandAAA = showEnableWcagAaa ? (totalChecksAAandAAA === 0 ? 0 : (passedChecksAAandAAA / totalChecksAAandAAA) * 100) : null;
208
+ const passedChecksAAandAAA = showEnableWcagAaa
209
+ ? totalChecksAAandAAA - wcagViolationsAAandAAA
210
+ : null;
211
+ // eslint-disable-next-line no-nested-ternary
212
+ const passPercentageAAandAAA = showEnableWcagAaa
213
+ ? totalChecksAAandAAA === 0
214
+ ? 0
215
+ : (passedChecksAAandAAA / totalChecksAAandAAA) * 100
216
+ : null;
215
217
 
216
218
  const wcagViolationsAA = wcagViolations.filter(violation => !wcagAAA.includes(violation)).length;
217
- const totalChecksAA = Object.keys(wcagLinksAAandAAA).filter(key => !wcagAAALinks.includes(key)).length;
219
+ const totalChecksAA = Object.keys(wcagLinksAAandAAA).filter(
220
+ key => !wcagAAALinks.includes(key),
221
+ ).length;
218
222
  const passedChecksAA = totalChecksAA - wcagViolationsAA;
219
223
  const passPercentageAA = totalChecksAA === 0 ? 0 : (passedChecksAA / totalChecksAA) * 100;
220
224
 
@@ -228,7 +232,294 @@ export const getWcagPassPercentage = (
228
232
  };
229
233
  };
230
234
 
231
- export const getFormattedTime = inputDate => {
235
+ export type IssueCategory = 'mustFix' | 'goodToFix' | 'needsReview' | 'passed';
236
+
237
+ export interface IssueDetail {
238
+ ruleId: string;
239
+ wcagConformance: string[];
240
+ occurrencesMustFix?: number;
241
+ occurrencesGoodToFix?: number;
242
+ occurrencesNeedsReview?: number;
243
+ occurrencesPassed: number;
244
+ }
245
+
246
+ export interface PageDetail {
247
+ pageTitle: string;
248
+ url: string;
249
+ totalOccurrencesFailedIncludingNeedsReview: number;
250
+ totalOccurrencesFailedExcludingNeedsReview: number;
251
+ totalOccurrencesMustFix?: number;
252
+ totalOccurrencesGoodToFix?: number;
253
+ totalOccurrencesNeedsReview: number;
254
+ totalOccurrencesPassed: number;
255
+ occurrencesExclusiveToNeedsReview: boolean;
256
+ typesOfIssuesCount: number;
257
+ typesOfIssuesExcludingNeedsReviewCount: number;
258
+ categoriesPresent: IssueCategory[];
259
+ conformance?: string[]; // WCAG levels as flexible strings
260
+ typesOfIssues: IssueDetail[];
261
+ }
262
+
263
+ export interface ScanPagesDetail {
264
+ oobeeAppVersion?: string;
265
+ pagesAffected: PageDetail[];
266
+ pagesNotAffected: PageDetail[];
267
+ scannedPagesCount: number;
268
+ pagesNotScanned: PageDetail[];
269
+ pagesNotScannedCount: number;
270
+ }
271
+
272
+ export const getProgressPercentage = (
273
+ scanPagesDetail: ScanPagesDetail,
274
+ showEnableWcagAaa: boolean,
275
+ ): {
276
+ averageProgressPercentageAA: string;
277
+ averageProgressPercentageAAandAAA: string;
278
+ } => {
279
+ const pages = scanPagesDetail.pagesAffected || [];
280
+
281
+ const progressPercentagesAA = pages.map((page: PageDetail) => {
282
+ const violations: string[] = page.conformance;
283
+ return getWcagPassPercentage(violations, showEnableWcagAaa).passPercentageAA;
284
+ });
285
+
286
+ const progressPercentagesAAandAAA = pages.map((page: PageDetail) => {
287
+ const violations: string[] = page.conformance;
288
+ return getWcagPassPercentage(violations, showEnableWcagAaa).passPercentageAAandAAA;
289
+ });
290
+
291
+ const totalAA = progressPercentagesAA.reduce((sum, p) => sum + parseFloat(p), 0);
292
+ const avgAA = progressPercentagesAA.length ? totalAA / progressPercentagesAA.length : 0;
293
+
294
+ const totalAAandAAA = progressPercentagesAAandAAA.reduce((sum, p) => sum + parseFloat(p), 0);
295
+ const avgAAandAAA = progressPercentagesAAandAAA.length
296
+ ? totalAAandAAA / progressPercentagesAAandAAA.length
297
+ : 0;
298
+
299
+ return {
300
+ averageProgressPercentageAA: avgAA.toFixed(2),
301
+ averageProgressPercentageAAandAAA: avgAAandAAA.toFixed(2),
302
+ };
303
+ };
304
+
305
+ export const getTotalRulesCount = async (
306
+ enableWcagAaa: boolean,
307
+ disableOobee: boolean,
308
+ ): Promise<{
309
+ totalRulesMustFix: number;
310
+ totalRulesGoodToFix: number;
311
+ totalRulesMustFixAndGoodToFix: number;
312
+ }> => {
313
+ const axeConfig = getAxeConfiguration({
314
+ enableWcagAaa,
315
+ gradingReadabilityFlag: '',
316
+ disableOobee,
317
+ });
318
+
319
+ // Get default rules from axe-core
320
+ const defaultRules = axe.getRules();
321
+
322
+ // Merge custom rules with default rules, converting RuleMetadata to Rule
323
+ const mergedRules: Rule[] = defaultRules.map(defaultRule => {
324
+ const customRule = axeConfig.rules.find(r => r.id === defaultRule.ruleId);
325
+ if (customRule) {
326
+ // Merge properties from customRule into defaultRule (RuleMetadata) to create a Rule
327
+ return {
328
+ id: defaultRule.ruleId,
329
+ enabled: customRule.enabled,
330
+ selector: customRule.selector,
331
+ any: customRule.any,
332
+ tags: defaultRule.tags,
333
+ metadata: customRule.metadata, // Use custom metadata if it exists
334
+ };
335
+ }
336
+ // Convert defaultRule (RuleMetadata) to Rule
337
+ return {
338
+ id: defaultRule.ruleId,
339
+ enabled: true, // Default to true if not overridden
340
+ tags: defaultRule.tags,
341
+ // No metadata here, since defaultRule.metadata might not exist
342
+ };
343
+ });
344
+
345
+ // Add any custom rules that don't override the default rules
346
+ axeConfig.rules.forEach(customRule => {
347
+ if (!mergedRules.some(mergedRule => mergedRule.id === customRule.id)) {
348
+ // Ensure customRule is of type Rule
349
+ const rule: Rule = {
350
+ id: customRule.id,
351
+ enabled: customRule.enabled,
352
+ selector: customRule.selector,
353
+ any: customRule.any,
354
+ tags: customRule.tags,
355
+ metadata: customRule.metadata,
356
+ // Add other properties if needed
357
+ };
358
+ mergedRules.push(rule);
359
+ }
360
+ });
361
+
362
+ // Apply the merged configuration to axe-core
363
+ axe.configure({ ...axeConfig, rules: mergedRules });
364
+
365
+ // ... (rest of your logic)
366
+ let totalRulesMustFix = 0;
367
+ let totalRulesGoodToFix = 0;
368
+
369
+ const wcagRegex = /^wcag\d+a+$/;
370
+
371
+ // Use mergedRules instead of rules to check enabled property
372
+ mergedRules.forEach(rule => {
373
+ if (!rule.enabled) {
374
+ return;
375
+ }
376
+
377
+ if (rule.id === 'frame-tested') return; // Ignore 'frame-tested' rule
378
+
379
+ const tags = rule.tags || [];
380
+
381
+ // Skip experimental and deprecated rules
382
+ if (tags.includes('experimental') || tags.includes('deprecated')) {
383
+ return;
384
+ }
385
+
386
+ const conformance = tags.filter(tag => tag.startsWith('wcag') || tag === 'best-practice');
387
+
388
+ // Ensure conformance level is sorted correctly
389
+ if (
390
+ conformance.length > 0 &&
391
+ conformance[0] !== 'best-practice' &&
392
+ !wcagRegex.test(conformance[0])
393
+ ) {
394
+ conformance.sort((a, b) => {
395
+ if (wcagRegex.test(a) && !wcagRegex.test(b)) {
396
+ return -1;
397
+ }
398
+ if (!wcagRegex.test(a) && wcagRegex.test(b)) {
399
+ return 1;
400
+ }
401
+ return 0;
402
+ });
403
+ }
404
+
405
+ if (conformance.includes('best-practice')) {
406
+ // console.log(`${totalRulesMustFix} Good To Fix: ${rule.id}`);
407
+
408
+ totalRulesGoodToFix += 1; // Categorized as "Good to Fix"
409
+ } else {
410
+ // console.log(`${totalRulesMustFix} Must Fix: ${rule.id}`);
411
+
412
+ totalRulesMustFix += 1; // Otherwise, it's "Must Fix"
413
+ }
414
+ });
415
+
416
+ return {
417
+ totalRulesMustFix,
418
+ totalRulesGoodToFix,
419
+ totalRulesMustFixAndGoodToFix: totalRulesMustFix + totalRulesGoodToFix,
420
+ };
421
+ };
422
+
423
+ export const getIssuesPercentage = async (
424
+ scanPagesDetail: ScanPagesDetail,
425
+ enableWcagAaa: boolean,
426
+ disableOobee: boolean,
427
+ ): Promise<{
428
+ avgTypesOfIssuesPercentageOfTotalRulesAtMustFix: string;
429
+ avgTypesOfIssuesPercentageOfTotalRulesAtGoodToFix: string;
430
+ avgTypesOfIssuesPercentageOfTotalRulesAtMustFixAndGoodToFix: string;
431
+ totalRulesMustFix: number;
432
+ totalRulesGoodToFix: number;
433
+ totalRulesMustFixAndGoodToFix: number;
434
+ avgTypesOfIssuesCountAtMustFix: string;
435
+ avgTypesOfIssuesCountAtGoodToFix: string;
436
+ avgTypesOfIssuesCountAtMustFixAndGoodToFix: string;
437
+ pagesAffectedPerRule: Record<string, number>;
438
+ pagesPercentageAffectedPerRule: Record<string, string>;
439
+ }> => {
440
+ const pages = scanPagesDetail.pagesAffected || [];
441
+ const totalPages = pages.length;
442
+
443
+ const pagesAffectedPerRule: Record<string, number> = {};
444
+
445
+ pages.forEach(page => {
446
+ page.typesOfIssues.forEach(issue => {
447
+ if ((issue.occurrencesMustFix || issue.occurrencesGoodToFix) > 0) {
448
+ pagesAffectedPerRule[issue.ruleId] = (pagesAffectedPerRule[issue.ruleId] || 0) + 1;
449
+ }
450
+ });
451
+ });
452
+
453
+ const pagesPercentageAffectedPerRule: Record<string, string> = {};
454
+ Object.entries(pagesAffectedPerRule).forEach(([ruleId, count]) => {
455
+ pagesPercentageAffectedPerRule[ruleId] =
456
+ totalPages > 0 ? ((count / totalPages) * 100).toFixed(2) : '0.00';
457
+ });
458
+
459
+ const typesOfIssuesCountAtMustFix = pages.map(
460
+ page => page.typesOfIssues.filter(issue => (issue.occurrencesMustFix || 0) > 0).length,
461
+ );
462
+
463
+ const typesOfIssuesCountAtGoodToFix = pages.map(
464
+ page => page.typesOfIssues.filter(issue => (issue.occurrencesGoodToFix || 0) > 0).length,
465
+ );
466
+
467
+ const typesOfIssuesCountSumMustFixAndGoodToFix = pages.map(
468
+ (_, index) =>
469
+ (typesOfIssuesCountAtMustFix[index] || 0) + (typesOfIssuesCountAtGoodToFix[index] || 0),
470
+ );
471
+
472
+ const { totalRulesMustFix, totalRulesGoodToFix, totalRulesMustFixAndGoodToFix } =
473
+ await getTotalRulesCount(enableWcagAaa, disableOobee);
474
+
475
+ const avgMustFixPerPage =
476
+ totalPages > 0
477
+ ? typesOfIssuesCountAtMustFix.reduce((sum, count) => sum + count, 0) / totalPages
478
+ : 0;
479
+
480
+ const avgGoodToFixPerPage =
481
+ totalPages > 0
482
+ ? typesOfIssuesCountAtGoodToFix.reduce((sum, count) => sum + count, 0) / totalPages
483
+ : 0;
484
+
485
+ const avgMustFixAndGoodToFixPerPage =
486
+ totalPages > 0
487
+ ? typesOfIssuesCountSumMustFixAndGoodToFix.reduce((sum, count) => sum + count, 0) / totalPages
488
+ : 0;
489
+
490
+ const avgTypesOfIssuesPercentageOfTotalRulesAtMustFix =
491
+ totalRulesMustFix > 0 ? ((avgMustFixPerPage / totalRulesMustFix) * 100).toFixed(2) : '0.00';
492
+
493
+ const avgTypesOfIssuesPercentageOfTotalRulesAtGoodToFix =
494
+ totalRulesGoodToFix > 0
495
+ ? ((avgGoodToFixPerPage / totalRulesGoodToFix) * 100).toFixed(2)
496
+ : '0.00';
497
+
498
+ const avgTypesOfIssuesPercentageOfTotalRulesAtMustFixAndGoodToFix =
499
+ totalRulesMustFixAndGoodToFix > 0
500
+ ? ((avgMustFixAndGoodToFixPerPage / totalRulesMustFixAndGoodToFix) * 100).toFixed(2)
501
+ : '0.00';
502
+
503
+ const avgTypesOfIssuesCountAtMustFix = avgMustFixPerPage.toFixed(2);
504
+ const avgTypesOfIssuesCountAtGoodToFix = avgGoodToFixPerPage.toFixed(2);
505
+ const avgTypesOfIssuesCountAtMustFixAndGoodToFix = avgMustFixAndGoodToFixPerPage.toFixed(2);
506
+
507
+ return {
508
+ avgTypesOfIssuesCountAtMustFix,
509
+ avgTypesOfIssuesCountAtGoodToFix,
510
+ avgTypesOfIssuesCountAtMustFixAndGoodToFix,
511
+ avgTypesOfIssuesPercentageOfTotalRulesAtMustFix,
512
+ avgTypesOfIssuesPercentageOfTotalRulesAtGoodToFix,
513
+ avgTypesOfIssuesPercentageOfTotalRulesAtMustFixAndGoodToFix,
514
+ totalRulesMustFix,
515
+ totalRulesGoodToFix,
516
+ totalRulesMustFixAndGoodToFix,
517
+ pagesAffectedPerRule,
518
+ pagesPercentageAffectedPerRule,
519
+ };
520
+ };
521
+
522
+ export const getFormattedTime = (inputDate: Date): string => {
232
523
  if (inputDate) {
233
524
  return inputDate.toLocaleTimeString('en-GB', {
234
525
  year: 'numeric',
@@ -250,7 +541,7 @@ export const getFormattedTime = inputDate => {
250
541
  });
251
542
  };
252
543
 
253
- export const formatDateTimeForMassScanner = date => {
544
+ export const formatDateTimeForMassScanner = (date: Date): string => {
254
545
  // Format date and time parts separately
255
546
  const year = date.getFullYear().toString().slice(-2); // Get the last two digits of the year
256
547
  const month = `0${date.getMonth() + 1}`.slice(-2); // Month is zero-indexed
@@ -271,14 +562,13 @@ export const setHeadlessMode = (browser: string, isHeadless: boolean): void => {
271
562
  } else {
272
563
  process.env.CRAWLEE_HEADLESS = '0';
273
564
  }
274
-
275
565
  };
276
566
 
277
- export const setThresholdLimits = setWarnLevel => {
567
+ export const setThresholdLimits = (setWarnLevel: string): void => {
278
568
  process.env.WARN_LEVEL = setWarnLevel;
279
569
  };
280
570
 
281
- export const zipResults = (zipName, resultsPath) => {
571
+ export const zipResults = (zipName: string, resultsPath: string): void => {
282
572
  // Check prior zip file exist and remove
283
573
  if (fs.existsSync(zipName)) {
284
574
  fs.unlinkSync(zipName);
@@ -309,9 +599,9 @@ export const zipResults = (zipName, resultsPath) => {
309
599
 
310
600
  // areLinksEqual compares 2 string URLs and ignores comparison of 'www.' and url protocol
311
601
  // i.e. 'http://google.com' and 'https://www.google.com' returns true
312
- export const areLinksEqual = (link1, link2) => {
602
+ export const areLinksEqual = (link1: string, link2: string): boolean => {
313
603
  try {
314
- const format = link => {
604
+ const format = (link: string): URL => {
315
605
  return new URL(link.replace(/www\./, ''));
316
606
  };
317
607
  const l1 = format(link1);
@@ -336,7 +626,7 @@ export const randomThreeDigitNumberString = () => {
336
626
  return String(threeDigitNumber);
337
627
  };
338
628
 
339
- export const isFollowStrategy = (link1, link2, rule) => {
629
+ export const isFollowStrategy = (link1: string, link2: string, rule: string): boolean => {
340
630
  const parsedLink1 = new URL(link1);
341
631
  const parsedLink2 = new URL(link2);
342
632
  if (rule === 'same-domain') {
@@ -347,17 +637,17 @@ export const isFollowStrategy = (link1, link2, rule) => {
347
637
  return parsedLink1.hostname === parsedLink2.hostname;
348
638
  };
349
639
 
350
- /* eslint-disable no-await-in-loop */
351
- export const retryFunction = async (func, maxAttempt) => {
640
+ export const retryFunction = async <T>(func: () => Promise<T>, maxAttempt: number): Promise<T> => {
352
641
  let attemptCount = 0;
353
642
  while (attemptCount < maxAttempt) {
354
643
  attemptCount += 1;
355
644
  try {
645
+ // eslint-disable-next-line no-await-in-loop
356
646
  const result = await func();
357
647
  return result;
358
648
  } catch (error) {
359
649
  silentLogger.error(`(Attempt count: ${attemptCount} of ${maxAttempt}) ${error}`);
360
650
  }
361
651
  }
652
+ throw new Error('Maximum number of attempts reached');
362
653
  };
363
- /* eslint-enable no-await-in-loop */