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