@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,305 @@
|
|
1
|
+
import { Options } from 'yargs';
|
2
|
+
import printMessage from 'print-message';
|
3
|
+
import { BrowserTypes, RuleFlags, ScannerTypes } from './constants.js';
|
4
|
+
|
5
|
+
export const messageOptions = {
|
6
|
+
border: false,
|
7
|
+
marginTop: 2,
|
8
|
+
marginBottom: 2,
|
9
|
+
};
|
10
|
+
|
11
|
+
export const alertMessageOptions = {
|
12
|
+
border: true,
|
13
|
+
borderColor: 'red',
|
14
|
+
};
|
15
|
+
|
16
|
+
export const cliOptions: { [key: string]: Options } = {
|
17
|
+
c: {
|
18
|
+
alias: 'scanner',
|
19
|
+
describe:
|
20
|
+
'Type of scan, 1) sitemap, 2) website crawl, 3) custom flow, 4) intelligent 5) local file',
|
21
|
+
requiresArg: true,
|
22
|
+
coerce: option => {
|
23
|
+
const choices = ['sitemap', 'website', 'custom', 'intelligent', 'localfile'];
|
24
|
+
let resolvedOption = option;
|
25
|
+
|
26
|
+
if (typeof option === 'number') {
|
27
|
+
// Will also allow integer choices
|
28
|
+
if (
|
29
|
+
Number.isInteger(resolvedOption) &&
|
30
|
+
resolvedOption > 0 &&
|
31
|
+
resolvedOption <= choices.length
|
32
|
+
) {
|
33
|
+
resolvedOption = choices[resolvedOption - 1];
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
switch (resolvedOption) {
|
38
|
+
case 'sitemap':
|
39
|
+
return ScannerTypes.SITEMAP;
|
40
|
+
case 'website':
|
41
|
+
return ScannerTypes.WEBSITE;
|
42
|
+
case 'custom':
|
43
|
+
return ScannerTypes.CUSTOM;
|
44
|
+
case 'localfile':
|
45
|
+
return ScannerTypes.LOCALFILE;
|
46
|
+
case 'intelligent':
|
47
|
+
return ScannerTypes.INTELLIGENT;
|
48
|
+
default:
|
49
|
+
printMessage(
|
50
|
+
[
|
51
|
+
`Invalid option: ${resolvedOption}`,
|
52
|
+
`Please enter an integer (1 to ${choices.length}) or keywords (${choices.join(', ')}).`,
|
53
|
+
],
|
54
|
+
messageOptions,
|
55
|
+
);
|
56
|
+
process.exit(1);
|
57
|
+
return null;
|
58
|
+
}
|
59
|
+
},
|
60
|
+
demandOption: true,
|
61
|
+
},
|
62
|
+
u: {
|
63
|
+
alias: 'url',
|
64
|
+
describe: 'Website URL you want to scan',
|
65
|
+
type: 'string',
|
66
|
+
demandOption: true,
|
67
|
+
},
|
68
|
+
d: {
|
69
|
+
alias: 'customDevice',
|
70
|
+
describe: 'Device you want to scan',
|
71
|
+
type: 'string',
|
72
|
+
demandOption: false,
|
73
|
+
},
|
74
|
+
w: {
|
75
|
+
alias: 'viewportWidth',
|
76
|
+
describe: 'Viewport width (in pixels) you want to scan',
|
77
|
+
type: 'number',
|
78
|
+
demandOption: false,
|
79
|
+
},
|
80
|
+
o: {
|
81
|
+
alias: 'zip',
|
82
|
+
describe: 'Zip filename to save results',
|
83
|
+
type: 'string',
|
84
|
+
demandOption: false,
|
85
|
+
},
|
86
|
+
p: {
|
87
|
+
alias: 'maxpages',
|
88
|
+
describe:
|
89
|
+
'Maximum number of pages to scan (default: 100). Only available in website and sitemap scans',
|
90
|
+
type: 'number',
|
91
|
+
demandOption: false,
|
92
|
+
},
|
93
|
+
f: {
|
94
|
+
alias: 'safeMode',
|
95
|
+
describe:
|
96
|
+
'Disable dynamically clicking of page buttons and links to find links, which resolve issues on some websites. [yes / no]',
|
97
|
+
type: 'string',
|
98
|
+
requiresArg: true,
|
99
|
+
default: 'no',
|
100
|
+
demandOption: false,
|
101
|
+
coerce: (value: string) => {
|
102
|
+
if (value.toLowerCase() === 'yes') {
|
103
|
+
return true;
|
104
|
+
}
|
105
|
+
if (value.toLowerCase() === 'no') {
|
106
|
+
return false;
|
107
|
+
}
|
108
|
+
throw new Error(`Invalid value "${value}" for -f, --safeMode. Use "yes" or "no".`);
|
109
|
+
},
|
110
|
+
},
|
111
|
+
h: {
|
112
|
+
alias: 'headless',
|
113
|
+
describe: 'Run the scan in headless mode. [yes / no]',
|
114
|
+
type: 'string',
|
115
|
+
requiresArg: true,
|
116
|
+
default: 'yes',
|
117
|
+
demandOption: false,
|
118
|
+
coerce: (value: string) => {
|
119
|
+
if (value.toLowerCase() === 'yes') {
|
120
|
+
return true;
|
121
|
+
}
|
122
|
+
if (value.toLowerCase() === 'no') {
|
123
|
+
return false;
|
124
|
+
}
|
125
|
+
throw new Error(`Invalid value "${value}" for -h, --headless. Use "yes" or "no".`);
|
126
|
+
},
|
127
|
+
},
|
128
|
+
b: {
|
129
|
+
alias: 'browserToRun',
|
130
|
+
describe: 'Browser to run the scan on: 1) Chromium, 2) Chrome, 3) Edge. Defaults to Chromium.',
|
131
|
+
requiresArg: true,
|
132
|
+
coerce: option => {
|
133
|
+
const choices = ['chromium', 'chrome', 'edge'];
|
134
|
+
let resolvedOption = option;
|
135
|
+
if (typeof option === 'number') {
|
136
|
+
// Will also allow integer choices
|
137
|
+
if (
|
138
|
+
Number.isInteger(resolvedOption) &&
|
139
|
+
resolvedOption > 0 &&
|
140
|
+
resolvedOption <= choices.length
|
141
|
+
) {
|
142
|
+
resolvedOption = choices[resolvedOption - 1];
|
143
|
+
}
|
144
|
+
}
|
145
|
+
|
146
|
+
switch (resolvedOption) {
|
147
|
+
case 'chromium':
|
148
|
+
return BrowserTypes.CHROMIUM;
|
149
|
+
case 'chrome':
|
150
|
+
return BrowserTypes.CHROME;
|
151
|
+
case 'edge':
|
152
|
+
return BrowserTypes.EDGE;
|
153
|
+
default:
|
154
|
+
printMessage(
|
155
|
+
[
|
156
|
+
`Invalid option: ${resolvedOption}`,
|
157
|
+
`Please enter an integer (1 to ${choices.length}) or keywords (${choices.join(', ')}).`,
|
158
|
+
],
|
159
|
+
messageOptions,
|
160
|
+
);
|
161
|
+
process.exit(1);
|
162
|
+
return null;
|
163
|
+
}
|
164
|
+
},
|
165
|
+
demandOption: false,
|
166
|
+
},
|
167
|
+
s: {
|
168
|
+
alias: 'strategy',
|
169
|
+
describe:
|
170
|
+
'Crawls up to general (same parent) domains, or only specific hostname. Defaults to "same-domain".',
|
171
|
+
choices: ['same-domain', 'same-hostname'],
|
172
|
+
requiresArg: true,
|
173
|
+
demandOption: false,
|
174
|
+
},
|
175
|
+
e: {
|
176
|
+
alias: 'exportDirectory',
|
177
|
+
describe: 'Preferred directory to store scan results. Path is relative to your home directory.',
|
178
|
+
type: 'string',
|
179
|
+
requiresArg: true,
|
180
|
+
demandOption: false,
|
181
|
+
},
|
182
|
+
j: {
|
183
|
+
alias: 'customFlowLabel',
|
184
|
+
describe: 'Give Custom Flow Scan a label for easier reference in the report',
|
185
|
+
type: 'string',
|
186
|
+
requiresArg: true,
|
187
|
+
demandOption: false,
|
188
|
+
},
|
189
|
+
k: {
|
190
|
+
alias: 'nameEmail',
|
191
|
+
describe: `To personalise your experience, we will be collecting your name, email address and app usage data. Your information fully complies with GovTech’s Privacy Policy. Please provide your name and email address in this format "John Doe:john@domain.com".`,
|
192
|
+
type: 'string',
|
193
|
+
demandOption: true,
|
194
|
+
},
|
195
|
+
t: {
|
196
|
+
alias: 'specifiedMaxConcurrency',
|
197
|
+
describe:
|
198
|
+
'Maximum number of pages to scan concurrently. Use for sites with throttling. Defaults to 25.',
|
199
|
+
type: 'number',
|
200
|
+
demandOption: false,
|
201
|
+
},
|
202
|
+
|
203
|
+
i: {
|
204
|
+
alias: 'fileTypes',
|
205
|
+
describe: 'File types to include in the scan. Defaults to html-only.',
|
206
|
+
type: 'string',
|
207
|
+
choices: ['all', 'pdf-only', 'html-only'],
|
208
|
+
demandOption: false,
|
209
|
+
requiresArg: true,
|
210
|
+
default: 'html-only',
|
211
|
+
},
|
212
|
+
x: {
|
213
|
+
alias: 'blacklistedPatternsFilename',
|
214
|
+
describe:
|
215
|
+
'Txt file that has a list of pattern of domains to exclude from accessibility scan separated by new line',
|
216
|
+
type: 'string',
|
217
|
+
demandOption: false,
|
218
|
+
},
|
219
|
+
a: {
|
220
|
+
alias: 'additional',
|
221
|
+
describe:
|
222
|
+
'Additional features to include in the report: \nscreenshots - Include element screenshots in the generated report \nnone - Exclude all additional features in the generated report',
|
223
|
+
type: 'string',
|
224
|
+
default: 'screenshots',
|
225
|
+
choices: ['screenshots', 'none'],
|
226
|
+
requiresArg: true,
|
227
|
+
demandOption: false,
|
228
|
+
},
|
229
|
+
q: {
|
230
|
+
alias: 'metadata',
|
231
|
+
describe:
|
232
|
+
'Json string that contains additional scan metadata for telemetry purposes. Defaults to "{}"',
|
233
|
+
type: 'string',
|
234
|
+
default: '{}',
|
235
|
+
demandOption: false,
|
236
|
+
},
|
237
|
+
r: {
|
238
|
+
alias: 'followRobots',
|
239
|
+
describe: 'Crawler adheres to robots.txt rules if it exists. [yes / no]',
|
240
|
+
type: 'string',
|
241
|
+
requiresArg: true,
|
242
|
+
default: 'no',
|
243
|
+
demandOption: false,
|
244
|
+
coerce: (value: string) => {
|
245
|
+
if (value.toLowerCase() === 'yes') {
|
246
|
+
return true;
|
247
|
+
}
|
248
|
+
if (value.toLowerCase() === 'no') {
|
249
|
+
return false;
|
250
|
+
}
|
251
|
+
throw new Error(`Invalid value "${value}" for -r, --followRobots. Use "yes" or "no".`);
|
252
|
+
},
|
253
|
+
},
|
254
|
+
m: {
|
255
|
+
alias: 'header',
|
256
|
+
describe:
|
257
|
+
'The HTTP authentication header keys and their respective values to enable crawler access to restricted resources.',
|
258
|
+
type: 'string',
|
259
|
+
requiresArg: true,
|
260
|
+
demandOption: false,
|
261
|
+
},
|
262
|
+
y: {
|
263
|
+
alias: 'ruleset',
|
264
|
+
describe: 'Specify scan ruleset for accessibility checks',
|
265
|
+
type: 'string',
|
266
|
+
choices: ['default', 'disable-oobee', 'enable-wcag-aaa', 'disable-oobee,enable-wcag-aaa'],
|
267
|
+
demandOption: false,
|
268
|
+
requiresArg: true,
|
269
|
+
default: 'default',
|
270
|
+
coerce: option => {
|
271
|
+
const validChoices = Object.values(RuleFlags);
|
272
|
+
const userChoices: string[] = option.split(',');
|
273
|
+
const invalidUserChoices = userChoices.filter(choice => !validChoices.includes(choice as RuleFlags));
|
274
|
+
if (invalidUserChoices.length > 0) {
|
275
|
+
printMessage(
|
276
|
+
[
|
277
|
+
`Invalid values ${invalidUserChoices.join(',')} for -y, --ruleset. Please provide valid values: ${validChoices.join(
|
278
|
+
', ',
|
279
|
+
)}.`,
|
280
|
+
],
|
281
|
+
messageOptions,
|
282
|
+
);
|
283
|
+
process.exit(1);
|
284
|
+
}
|
285
|
+
if (userChoices.length > 1 && userChoices.includes('default')) {
|
286
|
+
printMessage(
|
287
|
+
[
|
288
|
+
`default and ${userChoices.filter(choice => choice !== 'default').join(',')} are mutually exclusive`,
|
289
|
+
],
|
290
|
+
messageOptions,
|
291
|
+
);
|
292
|
+
process.exit(1);
|
293
|
+
}
|
294
|
+
return userChoices;
|
295
|
+
},
|
296
|
+
},
|
297
|
+
};
|
298
|
+
|
299
|
+
export const configureReportSetting = (isEnabled: boolean): void => {
|
300
|
+
if (isEnabled) {
|
301
|
+
process.env.REPORT_BREAKDOWN = '1';
|
302
|
+
} else {
|
303
|
+
process.env.REPORT_BREAKDOWN = '0';
|
304
|
+
}
|
305
|
+
};
|