@govtechsg/oobee 0.10.20
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/.dockerignore +22 -0
- package/.github/pull_request_template.md +11 -0
- package/.github/workflows/docker-test.yml +54 -0
- package/.github/workflows/image.yml +107 -0
- package/.github/workflows/publish.yml +18 -0
- package/.idea/modules.xml +8 -0
- package/.idea/purple-a11y.iml +9 -0
- package/.idea/vcs.xml +6 -0
- package/.prettierrc.json +12 -0
- package/.vscode/extensions.json +5 -0
- package/.vscode/settings.json +10 -0
- package/CODE_OF_CONDUCT.md +128 -0
- package/DETAILS.md +163 -0
- package/Dockerfile +60 -0
- package/INSTALLATION.md +146 -0
- package/INTEGRATION.md +785 -0
- package/LICENSE +22 -0
- package/README.md +587 -0
- package/SECURITY.md +5 -0
- package/__mocks__/mock-report.html +1431 -0
- package/__mocks__/mockFunctions.ts +32 -0
- package/__mocks__/mockIssues.ts +64 -0
- package/__mocks__/mock_all_issues/000000001.json +64 -0
- package/__mocks__/mock_all_issues/000000002.json +53 -0
- package/__mocks__/mock_all_issues/fake-file.txt +0 -0
- package/__tests__/logs.test.ts +25 -0
- package/__tests__/mergeAxeResults.test.ts +278 -0
- package/__tests__/utils.test.ts +118 -0
- package/a11y-scan-results.zip +0 -0
- package/eslint.config.js +53 -0
- package/exclusions.txt +2 -0
- package/gitlab-pipeline-template.yml +54 -0
- package/jest.config.js +1 -0
- package/package.json +96 -0
- package/scripts/copyFiles.js +44 -0
- package/scripts/install_oobee_dependencies.cmd +13 -0
- package/scripts/install_oobee_dependencies.command +101 -0
- package/scripts/install_oobee_dependencies.ps1 +110 -0
- package/scripts/oobee_shell.cmd +13 -0
- package/scripts/oobee_shell.command +11 -0
- package/scripts/oobee_shell.sh +55 -0
- package/scripts/oobee_shell_ps.ps1 +54 -0
- package/src/cli.ts +401 -0
- package/src/combine.ts +240 -0
- package/src/constants/__tests__/common.test.ts +44 -0
- package/src/constants/cliFunctions.ts +305 -0
- package/src/constants/common.ts +1840 -0
- package/src/constants/constants.ts +443 -0
- package/src/constants/errorMeta.json +319 -0
- package/src/constants/itemTypeDescription.ts +11 -0
- package/src/constants/oobeeAi.ts +141 -0
- package/src/constants/questions.ts +181 -0
- package/src/constants/sampleData.ts +187 -0
- package/src/crawlers/__tests__/commonCrawlerFunc.test.ts +51 -0
- package/src/crawlers/commonCrawlerFunc.ts +656 -0
- package/src/crawlers/crawlDomain.ts +877 -0
- package/src/crawlers/crawlIntelligentSitemap.ts +156 -0
- package/src/crawlers/crawlLocalFile.ts +193 -0
- package/src/crawlers/crawlSitemap.ts +356 -0
- package/src/crawlers/custom/extractAndGradeText.ts +57 -0
- package/src/crawlers/custom/flagUnlabelledClickableElements.ts +964 -0
- package/src/crawlers/custom/utils.ts +486 -0
- package/src/crawlers/customAxeFunctions.ts +82 -0
- package/src/crawlers/pdfScanFunc.ts +468 -0
- package/src/crawlers/runCustom.ts +117 -0
- package/src/index.ts +173 -0
- package/src/logs.ts +66 -0
- package/src/mergeAxeResults.ts +964 -0
- package/src/npmIndex.ts +284 -0
- package/src/screenshotFunc/htmlScreenshotFunc.ts +411 -0
- package/src/screenshotFunc/pdfScreenshotFunc.ts +762 -0
- package/src/static/ejs/partials/components/categorySelector.ejs +4 -0
- package/src/static/ejs/partials/components/categorySelectorDropdown.ejs +57 -0
- package/src/static/ejs/partials/components/pagesScannedModal.ejs +70 -0
- package/src/static/ejs/partials/components/reportSearch.ejs +47 -0
- package/src/static/ejs/partials/components/ruleOffcanvas.ejs +105 -0
- package/src/static/ejs/partials/components/scanAbout.ejs +263 -0
- package/src/static/ejs/partials/components/screenshotLightbox.ejs +13 -0
- package/src/static/ejs/partials/components/summaryScanAbout.ejs +141 -0
- package/src/static/ejs/partials/components/summaryScanResults.ejs +16 -0
- package/src/static/ejs/partials/components/summaryTable.ejs +20 -0
- package/src/static/ejs/partials/components/summaryWcagCompliance.ejs +94 -0
- package/src/static/ejs/partials/components/topFive.ejs +6 -0
- package/src/static/ejs/partials/components/wcagCompliance.ejs +70 -0
- package/src/static/ejs/partials/footer.ejs +21 -0
- package/src/static/ejs/partials/header.ejs +230 -0
- package/src/static/ejs/partials/main.ejs +40 -0
- package/src/static/ejs/partials/scripts/bootstrap.ejs +8 -0
- package/src/static/ejs/partials/scripts/categorySelectorDropdownScript.ejs +190 -0
- package/src/static/ejs/partials/scripts/categorySummary.ejs +141 -0
- package/src/static/ejs/partials/scripts/highlightjs.ejs +335 -0
- package/src/static/ejs/partials/scripts/popper.ejs +7 -0
- package/src/static/ejs/partials/scripts/reportSearch.ejs +248 -0
- package/src/static/ejs/partials/scripts/ruleOffcanvas.ejs +801 -0
- package/src/static/ejs/partials/scripts/screenshotLightbox.ejs +71 -0
- package/src/static/ejs/partials/scripts/summaryScanResults.ejs +14 -0
- package/src/static/ejs/partials/scripts/summaryTable.ejs +78 -0
- package/src/static/ejs/partials/scripts/utils.ejs +441 -0
- package/src/static/ejs/partials/styles/bootstrap.ejs +12375 -0
- package/src/static/ejs/partials/styles/highlightjs.ejs +54 -0
- package/src/static/ejs/partials/styles/styles.ejs +1843 -0
- package/src/static/ejs/partials/styles/summaryBootstrap.ejs +12458 -0
- package/src/static/ejs/partials/summaryHeader.ejs +70 -0
- package/src/static/ejs/partials/summaryMain.ejs +75 -0
- package/src/static/ejs/report.ejs +420 -0
- package/src/static/ejs/summary.ejs +47 -0
- package/src/static/mustache/.prettierrc +4 -0
- package/src/static/mustache/Attention Deficit.mustache +11 -0
- package/src/static/mustache/Blind.mustache +11 -0
- package/src/static/mustache/Cognitive.mustache +7 -0
- package/src/static/mustache/Colorblindness.mustache +20 -0
- package/src/static/mustache/Deaf.mustache +12 -0
- package/src/static/mustache/Deafblind.mustache +7 -0
- package/src/static/mustache/Dyslexia.mustache +14 -0
- package/src/static/mustache/Low Vision.mustache +7 -0
- package/src/static/mustache/Mobility.mustache +15 -0
- package/src/static/mustache/Sighted Keyboard Users.mustache +42 -0
- package/src/static/mustache/report.mustache +1709 -0
- package/src/types/print-message.d.ts +28 -0
- package/src/types/types.ts +46 -0
- package/src/types/xpath-to-css.d.ts +3 -0
- package/src/utils.ts +332 -0
- package/tsconfig.json +15 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
declare module 'print-message' {
|
2
|
+
interface PrintConfig {
|
3
|
+
border?: boolean;
|
4
|
+
border?: boolean; // Enable border
|
5
|
+
color?: string; // Default text color from console
|
6
|
+
borderColor?: string; // Border color is yellow
|
7
|
+
borderSymbol?: string; // Symbol for top/bottom borders
|
8
|
+
sideSymbol?: string; // Symbol for left/right borders
|
9
|
+
leftTopSymbol?: string; // Symbol that uses for left top corner
|
10
|
+
leftBottomSymbol?: string; // Symbol that uses for left bottom corner
|
11
|
+
rightTopSymbol?: string; // Symbol that uses for right top corner
|
12
|
+
rightBottomSymbol?: string; // Symbol that uses for right bottom corner
|
13
|
+
marginTop?: number; // Margin before border begins
|
14
|
+
marginBottom?: number; // Margin after border ends
|
15
|
+
paddingTop?: number; // Padding after border begins
|
16
|
+
paddingBottom?: number; // Padding before border ends
|
17
|
+
}
|
18
|
+
|
19
|
+
/**
|
20
|
+
* Print messages to console.
|
21
|
+
*
|
22
|
+
* @param lines Array of lines
|
23
|
+
* @param config Additional params for print
|
24
|
+
*/
|
25
|
+
function print(lines: string[], config?: PrintConfig): void;
|
26
|
+
|
27
|
+
export = print;
|
28
|
+
}
|
@@ -0,0 +1,46 @@
|
|
1
|
+
import { ScannerTypes, UrlsCrawled } from '../constants/constants.js';
|
2
|
+
|
3
|
+
export type ViewportSize = {
|
4
|
+
width: number;
|
5
|
+
height: number;
|
6
|
+
};
|
7
|
+
|
8
|
+
export interface IBboxLocation {
|
9
|
+
// page: number;
|
10
|
+
location: string;
|
11
|
+
isVisible?: boolean;
|
12
|
+
groupId?: string;
|
13
|
+
bboxTitle?: string;
|
14
|
+
}
|
15
|
+
|
16
|
+
export type StructureTree = {
|
17
|
+
name: string;
|
18
|
+
children: StructureTree[] | StructureTree;
|
19
|
+
ref: { num: number; gen: number };
|
20
|
+
mcid?: number;
|
21
|
+
pageIndex?: number;
|
22
|
+
};
|
23
|
+
|
24
|
+
type DeviceDescriptor = {
|
25
|
+
viewport: ViewportSize;
|
26
|
+
userAgent: string;
|
27
|
+
deviceScaleFactor: number;
|
28
|
+
isMobile: boolean;
|
29
|
+
hasTouch: boolean;
|
30
|
+
defaultBrowserType: 'chromium' | 'firefox' | 'webkit';
|
31
|
+
};
|
32
|
+
|
33
|
+
export type viewportSettings = {
|
34
|
+
deviceChosen: string;
|
35
|
+
customDevice: string;
|
36
|
+
viewportWidth: number;
|
37
|
+
playwrightDeviceDetailsObject: DeviceDescriptor;
|
38
|
+
};
|
39
|
+
|
40
|
+
export type ScanDetails = {
|
41
|
+
startTime: Date;
|
42
|
+
endTime: Date;
|
43
|
+
crawlType: ScannerTypes;
|
44
|
+
requestUrl: URL;
|
45
|
+
urlsCrawled: UrlsCrawled;
|
46
|
+
};
|
package/src/utils.ts
ADDED
@@ -0,0 +1,332 @@
|
|
1
|
+
import { execSync, spawnSync } from 'child_process';
|
2
|
+
import path from 'path';
|
3
|
+
import os from 'os';
|
4
|
+
import fs from 'fs-extra';
|
5
|
+
import constants, {
|
6
|
+
BrowserTypes,
|
7
|
+
destinationPath,
|
8
|
+
getIntermediateScreenshotsPath,
|
9
|
+
} from './constants/constants.js';
|
10
|
+
import { silentLogger } from './logs.js';
|
11
|
+
|
12
|
+
export const getVersion = () => {
|
13
|
+
const loadJSON = filePath =>
|
14
|
+
JSON.parse(fs.readFileSync(new URL(filePath, import.meta.url)).toString());
|
15
|
+
const versionNum = loadJSON('../package.json').version;
|
16
|
+
|
17
|
+
return versionNum;
|
18
|
+
};
|
19
|
+
|
20
|
+
export const getHost = url => new URL(url).host;
|
21
|
+
|
22
|
+
export const getCurrentDate = () => {
|
23
|
+
const date = new Date();
|
24
|
+
return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`;
|
25
|
+
};
|
26
|
+
|
27
|
+
export const isWhitelistedContentType = contentType => {
|
28
|
+
const whitelist = ['text/html'];
|
29
|
+
return whitelist.filter(type => contentType.trim().startsWith(type)).length === 1;
|
30
|
+
};
|
31
|
+
|
32
|
+
export const getStoragePath = (randomToken: string): string => {
|
33
|
+
if (process.env.OOBEE_VERBOSE_STORAGE_PATH) {
|
34
|
+
return `${process.env.OOBEE_VERBOSE_STORAGE_PATH}/${randomToken}`;
|
35
|
+
}
|
36
|
+
if (constants.exportDirectory === process.cwd()) {
|
37
|
+
return `results/${randomToken}`;
|
38
|
+
}
|
39
|
+
if (!path.isAbsolute(constants.exportDirectory)) {
|
40
|
+
constants.exportDirectory = path.resolve(process.cwd(), constants.exportDirectory);
|
41
|
+
}
|
42
|
+
return `${constants.exportDirectory}/${randomToken}`;
|
43
|
+
};
|
44
|
+
|
45
|
+
export const createDetailsAndLogs = async randomToken => {
|
46
|
+
const storagePath = getStoragePath(randomToken);
|
47
|
+
const logPath = `logs/${randomToken}`;
|
48
|
+
try {
|
49
|
+
await fs.ensureDir(storagePath);
|
50
|
+
|
51
|
+
// update logs
|
52
|
+
await fs.ensureDir(logPath);
|
53
|
+
await fs.pathExists('errors.txt').then(async exists => {
|
54
|
+
if (exists) {
|
55
|
+
try {
|
56
|
+
await fs.copy('errors.txt', `${logPath}/${randomToken}.txt`);
|
57
|
+
} catch (error) {
|
58
|
+
if (error.code === 'EBUSY') {
|
59
|
+
console.log(
|
60
|
+
`Unable to copy the file from 'errors.txt' to '${logPath}/${randomToken}.txt' because it is currently in use.`,
|
61
|
+
);
|
62
|
+
console.log(
|
63
|
+
'Please close any applications that might be using this file and try again.',
|
64
|
+
);
|
65
|
+
} else {
|
66
|
+
console.log(`An unexpected error occurred while copying the file: ${error.message}`);
|
67
|
+
}
|
68
|
+
}
|
69
|
+
}
|
70
|
+
});
|
71
|
+
} catch (error) {
|
72
|
+
console.log(`An error occurred while setting up storage or log directories: ${error.message}`);
|
73
|
+
}
|
74
|
+
};
|
75
|
+
|
76
|
+
export const getUserDataFilePath = () => {
|
77
|
+
const platform = os.platform();
|
78
|
+
if (platform === 'win32') {
|
79
|
+
return path.join(process.env.APPDATA, 'Oobee', 'userData.txt');
|
80
|
+
}
|
81
|
+
if (platform === 'darwin') {
|
82
|
+
return path.join(process.env.HOME, 'Library', 'Application Support', 'Oobee', 'userData.txt');
|
83
|
+
}
|
84
|
+
// linux and other OS
|
85
|
+
return path.join(process.env.HOME, '.config', 'oobee', 'userData.txt');
|
86
|
+
};
|
87
|
+
|
88
|
+
export const getUserDataTxt = () => {
|
89
|
+
const textFilePath = getUserDataFilePath();
|
90
|
+
|
91
|
+
// check if textFilePath exists
|
92
|
+
if (fs.existsSync(textFilePath)) {
|
93
|
+
const userData = JSON.parse(fs.readFileSync(textFilePath, 'utf8'));
|
94
|
+
return userData;
|
95
|
+
}
|
96
|
+
return null;
|
97
|
+
};
|
98
|
+
|
99
|
+
export const writeToUserDataTxt = async (key, value) => {
|
100
|
+
const textFilePath = getUserDataFilePath();
|
101
|
+
|
102
|
+
// Create file if it doesn't exist
|
103
|
+
if (fs.existsSync(textFilePath)) {
|
104
|
+
const userData = JSON.parse(fs.readFileSync(textFilePath, 'utf8'));
|
105
|
+
userData[key] = value;
|
106
|
+
fs.writeFileSync(textFilePath, JSON.stringify(userData, null, 2));
|
107
|
+
} else {
|
108
|
+
const textFilePathDir = path.dirname(textFilePath);
|
109
|
+
if (!fs.existsSync(textFilePathDir)) {
|
110
|
+
fs.mkdirSync(textFilePathDir, { recursive: true });
|
111
|
+
}
|
112
|
+
fs.appendFileSync(textFilePath, JSON.stringify({ [key]: value }, null, 2));
|
113
|
+
}
|
114
|
+
};
|
115
|
+
|
116
|
+
export const createAndUpdateResultsFolders = async randomToken => {
|
117
|
+
const storagePath = getStoragePath(randomToken);
|
118
|
+
await fs.ensureDir(`${storagePath}`);
|
119
|
+
|
120
|
+
const intermediatePdfResultsPath = `${randomToken}/${constants.pdfScanResultFileName}`;
|
121
|
+
|
122
|
+
const transferResults = async (intermPath, resultFile) => {
|
123
|
+
try {
|
124
|
+
if (fs.existsSync(intermPath)) {
|
125
|
+
await fs.copy(intermPath, `${storagePath}/${resultFile}`);
|
126
|
+
}
|
127
|
+
} catch (error) {
|
128
|
+
if (error.code === 'EBUSY') {
|
129
|
+
console.log(
|
130
|
+
`Unable to copy the file from ${intermPath} to ${storagePath}/${resultFile} because it is currently in use.`,
|
131
|
+
);
|
132
|
+
console.log('Please close any applications that might be using this file and try again.');
|
133
|
+
} else {
|
134
|
+
console.log(
|
135
|
+
`An unexpected error occurred while copying the file from ${intermPath} to ${storagePath}/${resultFile}: ${error.message}`,
|
136
|
+
);
|
137
|
+
}
|
138
|
+
}
|
139
|
+
};
|
140
|
+
|
141
|
+
await Promise.all([transferResults(intermediatePdfResultsPath, constants.pdfScanResultFileName)]);
|
142
|
+
};
|
143
|
+
|
144
|
+
export const createScreenshotsFolder = randomToken => {
|
145
|
+
const storagePath = getStoragePath(randomToken);
|
146
|
+
const intermediateScreenshotsPath = getIntermediateScreenshotsPath(randomToken);
|
147
|
+
if (fs.existsSync(intermediateScreenshotsPath)) {
|
148
|
+
fs.readdir(intermediateScreenshotsPath, (err, files) => {
|
149
|
+
if (err) {
|
150
|
+
console.log(`Screenshots were not moved successfully: ${err.message}`);
|
151
|
+
}
|
152
|
+
|
153
|
+
if (!fs.existsSync(destinationPath(storagePath))) {
|
154
|
+
try {
|
155
|
+
fs.mkdirSync(destinationPath(storagePath), { recursive: true });
|
156
|
+
} catch (error) {
|
157
|
+
console.error('Screenshots folder was not created successfully:', error);
|
158
|
+
}
|
159
|
+
}
|
160
|
+
|
161
|
+
files.forEach(file => {
|
162
|
+
fs.renameSync(
|
163
|
+
`${intermediateScreenshotsPath}/${file}`,
|
164
|
+
`${destinationPath(storagePath)}/${file}`,
|
165
|
+
);
|
166
|
+
});
|
167
|
+
|
168
|
+
fs.rmdir(intermediateScreenshotsPath, rmdirErr => {
|
169
|
+
if (rmdirErr) {
|
170
|
+
console.log(rmdirErr);
|
171
|
+
}
|
172
|
+
});
|
173
|
+
});
|
174
|
+
}
|
175
|
+
};
|
176
|
+
|
177
|
+
export const cleanUp = async pathToDelete => {
|
178
|
+
fs.removeSync(pathToDelete);
|
179
|
+
};
|
180
|
+
|
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
|
+
export const getWcagPassPercentage = (wcagViolations: string[]): string => {
|
194
|
+
const totalChecks = Object.keys(constants.wcagLinks).length;
|
195
|
+
const passedChecks = totalChecks - wcagViolations.length;
|
196
|
+
const passPercentage = (passedChecks / totalChecks) * 100;
|
197
|
+
|
198
|
+
return passPercentage.toFixed(2); // toFixed returns a string, which is correct here
|
199
|
+
};
|
200
|
+
|
201
|
+
export const getFormattedTime = inputDate => {
|
202
|
+
if (inputDate) {
|
203
|
+
return inputDate.toLocaleTimeString('en-GB', {
|
204
|
+
year: 'numeric',
|
205
|
+
month: 'short',
|
206
|
+
day: 'numeric',
|
207
|
+
hour12: false,
|
208
|
+
hour: 'numeric',
|
209
|
+
minute: '2-digit',
|
210
|
+
});
|
211
|
+
}
|
212
|
+
return new Date().toLocaleTimeString('en-GB', {
|
213
|
+
year: 'numeric',
|
214
|
+
month: 'short',
|
215
|
+
day: 'numeric',
|
216
|
+
hour12: false,
|
217
|
+
hour: 'numeric',
|
218
|
+
minute: '2-digit',
|
219
|
+
timeZoneName: 'longGeneric',
|
220
|
+
});
|
221
|
+
};
|
222
|
+
|
223
|
+
export const formatDateTimeForMassScanner = date => {
|
224
|
+
// Format date and time parts separately
|
225
|
+
const year = date.getFullYear().toString().slice(-2); // Get the last two digits of the year
|
226
|
+
const month = `0${date.getMonth() + 1}`.slice(-2); // Month is zero-indexed
|
227
|
+
const day = `0${date.getDate()}`.slice(-2);
|
228
|
+
const hour = `0${date.getHours()}`.slice(-2);
|
229
|
+
const minute = `0${date.getMinutes()}`.slice(-2);
|
230
|
+
|
231
|
+
// Combine formatted date and time with a slash
|
232
|
+
const formattedDateTime = `${day}/${month}/${year} ${hour}:${minute}`;
|
233
|
+
|
234
|
+
return formattedDateTime;
|
235
|
+
};
|
236
|
+
|
237
|
+
export const setHeadlessMode = (browser: string, isHeadless: boolean): void => {
|
238
|
+
const isWindowsOSAndEdgeBrowser = browser === BrowserTypes.EDGE && os.platform() === 'win32';
|
239
|
+
if (isHeadless || isWindowsOSAndEdgeBrowser) {
|
240
|
+
process.env.CRAWLEE_HEADLESS = '1';
|
241
|
+
} else {
|
242
|
+
process.env.CRAWLEE_HEADLESS = '0';
|
243
|
+
}
|
244
|
+
};
|
245
|
+
|
246
|
+
export const setThresholdLimits = setWarnLevel => {
|
247
|
+
process.env.WARN_LEVEL = setWarnLevel;
|
248
|
+
};
|
249
|
+
|
250
|
+
export const zipResults = (zipName, resultsPath) => {
|
251
|
+
// Check prior zip file exist and remove
|
252
|
+
if (fs.existsSync(zipName)) {
|
253
|
+
fs.unlinkSync(zipName);
|
254
|
+
}
|
255
|
+
|
256
|
+
if (os.platform() === 'win32') {
|
257
|
+
execSync(
|
258
|
+
`Get-ChildItem -Path "${resultsPath}\\*.*" -Recurse | Compress-Archive -DestinationPath "${zipName}"`,
|
259
|
+
{ shell: 'powershell.exe' },
|
260
|
+
);
|
261
|
+
} else {
|
262
|
+
// Get zip command in Mac and Linux
|
263
|
+
const command = '/usr/bin/zip';
|
264
|
+
// Check if user specified absolute or relative path
|
265
|
+
const zipFilePath = path.isAbsolute(zipName) ? zipName : path.join(process.cwd(), zipName);
|
266
|
+
|
267
|
+
// To zip up files recursively (-r) in the results folder path and write it to user's specified path
|
268
|
+
const args = ['-r', zipFilePath, '.'];
|
269
|
+
|
270
|
+
// Change working directory only for the zip command
|
271
|
+
const options = {
|
272
|
+
cwd: resultsPath,
|
273
|
+
};
|
274
|
+
|
275
|
+
spawnSync(command, args, options);
|
276
|
+
}
|
277
|
+
};
|
278
|
+
|
279
|
+
// areLinksEqual compares 2 string URLs and ignores comparison of 'www.' and url protocol
|
280
|
+
// i.e. 'http://google.com' and 'https://www.google.com' returns true
|
281
|
+
export const areLinksEqual = (link1, link2) => {
|
282
|
+
try {
|
283
|
+
const format = link => {
|
284
|
+
return new URL(link.replace(/www\./, ''));
|
285
|
+
};
|
286
|
+
const l1 = format(link1);
|
287
|
+
const l2 = format(link2);
|
288
|
+
|
289
|
+
const areHostEqual = l1.host === l2.host;
|
290
|
+
const arePathEqual = l1.pathname === l2.pathname;
|
291
|
+
|
292
|
+
return areHostEqual && arePathEqual;
|
293
|
+
} catch {
|
294
|
+
return link1 === link2;
|
295
|
+
}
|
296
|
+
};
|
297
|
+
|
298
|
+
export const randomThreeDigitNumberString = () => {
|
299
|
+
// Generate a random decimal between 0 (inclusive) and 1 (exclusive)
|
300
|
+
const randomDecimal = Math.random();
|
301
|
+
// Multiply by 900 to get a decimal between 0 (inclusive) and 900 (exclusive)
|
302
|
+
const scaledDecimal = randomDecimal * 900;
|
303
|
+
// Add 100 to ensure the result is between 100 (inclusive) and 1000 (exclusive)
|
304
|
+
const threeDigitNumber = Math.floor(scaledDecimal) + 100;
|
305
|
+
return String(threeDigitNumber);
|
306
|
+
};
|
307
|
+
|
308
|
+
export const isFollowStrategy = (link1, link2, rule) => {
|
309
|
+
const parsedLink1 = new URL(link1);
|
310
|
+
const parsedLink2 = new URL(link2);
|
311
|
+
if (rule === 'same-domain') {
|
312
|
+
const link1Domain = parsedLink1.hostname.split('.').slice(-2).join('.');
|
313
|
+
const link2Domain = parsedLink2.hostname.split('.').slice(-2).join('.');
|
314
|
+
return link1Domain === link2Domain;
|
315
|
+
}
|
316
|
+
return parsedLink1.hostname === parsedLink2.hostname;
|
317
|
+
};
|
318
|
+
|
319
|
+
/* eslint-disable no-await-in-loop */
|
320
|
+
export const retryFunction = async (func, maxAttempt) => {
|
321
|
+
let attemptCount = 0;
|
322
|
+
while (attemptCount < maxAttempt) {
|
323
|
+
attemptCount += 1;
|
324
|
+
try {
|
325
|
+
const result = await func();
|
326
|
+
return result;
|
327
|
+
} catch (error) {
|
328
|
+
silentLogger.error(`(Attempt count: ${attemptCount} of ${maxAttempt}) ${error}`);
|
329
|
+
}
|
330
|
+
}
|
331
|
+
};
|
332
|
+
/* eslint-enable no-await-in-loop */
|
package/tsconfig.json
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
{
|
2
|
+
"compilerOptions": {
|
3
|
+
"outDir": "./dist",
|
4
|
+
"allowJs": true,
|
5
|
+
"target": "es2021",
|
6
|
+
"module": "nodenext",
|
7
|
+
"rootDir": "./src",
|
8
|
+
"skipLibCheck": true
|
9
|
+
},
|
10
|
+
"include": ["./src/**/*"],
|
11
|
+
"exclude": [
|
12
|
+
"node_modules",
|
13
|
+
"**/*.test.ts"
|
14
|
+
]
|
15
|
+
}
|