@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.
Files changed (123) hide show
  1. package/.dockerignore +22 -0
  2. package/.github/pull_request_template.md +11 -0
  3. package/.github/workflows/docker-test.yml +54 -0
  4. package/.github/workflows/image.yml +107 -0
  5. package/.github/workflows/publish.yml +18 -0
  6. package/.idea/modules.xml +8 -0
  7. package/.idea/purple-a11y.iml +9 -0
  8. package/.idea/vcs.xml +6 -0
  9. package/.prettierrc.json +12 -0
  10. package/.vscode/extensions.json +5 -0
  11. package/.vscode/settings.json +10 -0
  12. package/CODE_OF_CONDUCT.md +128 -0
  13. package/DETAILS.md +163 -0
  14. package/Dockerfile +60 -0
  15. package/INSTALLATION.md +146 -0
  16. package/INTEGRATION.md +785 -0
  17. package/LICENSE +22 -0
  18. package/README.md +587 -0
  19. package/SECURITY.md +5 -0
  20. package/__mocks__/mock-report.html +1431 -0
  21. package/__mocks__/mockFunctions.ts +32 -0
  22. package/__mocks__/mockIssues.ts +64 -0
  23. package/__mocks__/mock_all_issues/000000001.json +64 -0
  24. package/__mocks__/mock_all_issues/000000002.json +53 -0
  25. package/__mocks__/mock_all_issues/fake-file.txt +0 -0
  26. package/__tests__/logs.test.ts +25 -0
  27. package/__tests__/mergeAxeResults.test.ts +278 -0
  28. package/__tests__/utils.test.ts +118 -0
  29. package/a11y-scan-results.zip +0 -0
  30. package/eslint.config.js +53 -0
  31. package/exclusions.txt +2 -0
  32. package/gitlab-pipeline-template.yml +54 -0
  33. package/jest.config.js +1 -0
  34. package/package.json +96 -0
  35. package/scripts/copyFiles.js +44 -0
  36. package/scripts/install_oobee_dependencies.cmd +13 -0
  37. package/scripts/install_oobee_dependencies.command +101 -0
  38. package/scripts/install_oobee_dependencies.ps1 +110 -0
  39. package/scripts/oobee_shell.cmd +13 -0
  40. package/scripts/oobee_shell.command +11 -0
  41. package/scripts/oobee_shell.sh +55 -0
  42. package/scripts/oobee_shell_ps.ps1 +54 -0
  43. package/src/cli.ts +401 -0
  44. package/src/combine.ts +240 -0
  45. package/src/constants/__tests__/common.test.ts +44 -0
  46. package/src/constants/cliFunctions.ts +305 -0
  47. package/src/constants/common.ts +1840 -0
  48. package/src/constants/constants.ts +443 -0
  49. package/src/constants/errorMeta.json +319 -0
  50. package/src/constants/itemTypeDescription.ts +11 -0
  51. package/src/constants/oobeeAi.ts +141 -0
  52. package/src/constants/questions.ts +181 -0
  53. package/src/constants/sampleData.ts +187 -0
  54. package/src/crawlers/__tests__/commonCrawlerFunc.test.ts +51 -0
  55. package/src/crawlers/commonCrawlerFunc.ts +656 -0
  56. package/src/crawlers/crawlDomain.ts +877 -0
  57. package/src/crawlers/crawlIntelligentSitemap.ts +156 -0
  58. package/src/crawlers/crawlLocalFile.ts +193 -0
  59. package/src/crawlers/crawlSitemap.ts +356 -0
  60. package/src/crawlers/custom/extractAndGradeText.ts +57 -0
  61. package/src/crawlers/custom/flagUnlabelledClickableElements.ts +964 -0
  62. package/src/crawlers/custom/utils.ts +486 -0
  63. package/src/crawlers/customAxeFunctions.ts +82 -0
  64. package/src/crawlers/pdfScanFunc.ts +468 -0
  65. package/src/crawlers/runCustom.ts +117 -0
  66. package/src/index.ts +173 -0
  67. package/src/logs.ts +66 -0
  68. package/src/mergeAxeResults.ts +964 -0
  69. package/src/npmIndex.ts +284 -0
  70. package/src/screenshotFunc/htmlScreenshotFunc.ts +411 -0
  71. package/src/screenshotFunc/pdfScreenshotFunc.ts +762 -0
  72. package/src/static/ejs/partials/components/categorySelector.ejs +4 -0
  73. package/src/static/ejs/partials/components/categorySelectorDropdown.ejs +57 -0
  74. package/src/static/ejs/partials/components/pagesScannedModal.ejs +70 -0
  75. package/src/static/ejs/partials/components/reportSearch.ejs +47 -0
  76. package/src/static/ejs/partials/components/ruleOffcanvas.ejs +105 -0
  77. package/src/static/ejs/partials/components/scanAbout.ejs +263 -0
  78. package/src/static/ejs/partials/components/screenshotLightbox.ejs +13 -0
  79. package/src/static/ejs/partials/components/summaryScanAbout.ejs +141 -0
  80. package/src/static/ejs/partials/components/summaryScanResults.ejs +16 -0
  81. package/src/static/ejs/partials/components/summaryTable.ejs +20 -0
  82. package/src/static/ejs/partials/components/summaryWcagCompliance.ejs +94 -0
  83. package/src/static/ejs/partials/components/topFive.ejs +6 -0
  84. package/src/static/ejs/partials/components/wcagCompliance.ejs +70 -0
  85. package/src/static/ejs/partials/footer.ejs +21 -0
  86. package/src/static/ejs/partials/header.ejs +230 -0
  87. package/src/static/ejs/partials/main.ejs +40 -0
  88. package/src/static/ejs/partials/scripts/bootstrap.ejs +8 -0
  89. package/src/static/ejs/partials/scripts/categorySelectorDropdownScript.ejs +190 -0
  90. package/src/static/ejs/partials/scripts/categorySummary.ejs +141 -0
  91. package/src/static/ejs/partials/scripts/highlightjs.ejs +335 -0
  92. package/src/static/ejs/partials/scripts/popper.ejs +7 -0
  93. package/src/static/ejs/partials/scripts/reportSearch.ejs +248 -0
  94. package/src/static/ejs/partials/scripts/ruleOffcanvas.ejs +801 -0
  95. package/src/static/ejs/partials/scripts/screenshotLightbox.ejs +71 -0
  96. package/src/static/ejs/partials/scripts/summaryScanResults.ejs +14 -0
  97. package/src/static/ejs/partials/scripts/summaryTable.ejs +78 -0
  98. package/src/static/ejs/partials/scripts/utils.ejs +441 -0
  99. package/src/static/ejs/partials/styles/bootstrap.ejs +12375 -0
  100. package/src/static/ejs/partials/styles/highlightjs.ejs +54 -0
  101. package/src/static/ejs/partials/styles/styles.ejs +1843 -0
  102. package/src/static/ejs/partials/styles/summaryBootstrap.ejs +12458 -0
  103. package/src/static/ejs/partials/summaryHeader.ejs +70 -0
  104. package/src/static/ejs/partials/summaryMain.ejs +75 -0
  105. package/src/static/ejs/report.ejs +420 -0
  106. package/src/static/ejs/summary.ejs +47 -0
  107. package/src/static/mustache/.prettierrc +4 -0
  108. package/src/static/mustache/Attention Deficit.mustache +11 -0
  109. package/src/static/mustache/Blind.mustache +11 -0
  110. package/src/static/mustache/Cognitive.mustache +7 -0
  111. package/src/static/mustache/Colorblindness.mustache +20 -0
  112. package/src/static/mustache/Deaf.mustache +12 -0
  113. package/src/static/mustache/Deafblind.mustache +7 -0
  114. package/src/static/mustache/Dyslexia.mustache +14 -0
  115. package/src/static/mustache/Low Vision.mustache +7 -0
  116. package/src/static/mustache/Mobility.mustache +15 -0
  117. package/src/static/mustache/Sighted Keyboard Users.mustache +42 -0
  118. package/src/static/mustache/report.mustache +1709 -0
  119. package/src/types/print-message.d.ts +28 -0
  120. package/src/types/types.ts +46 -0
  121. package/src/types/xpath-to-css.d.ts +3 -0
  122. package/src/utils.ts +332 -0
  123. package/tsconfig.json +15 -0
package/src/index.ts ADDED
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env node
2
+ /* eslint-disable func-names */
3
+ /* eslint-disable no-param-reassign */
4
+ import printMessage from 'print-message';
5
+ import inquirer from 'inquirer';
6
+ import { EnqueueStrategy } from 'crawlee';
7
+ import {
8
+ getVersion,
9
+ cleanUp,
10
+ setHeadlessMode,
11
+ getUserDataTxt,
12
+ writeToUserDataTxt,
13
+ } from './utils.js';
14
+ import {
15
+ prepareData,
16
+ messageOptions,
17
+ getPlaywrightDeviceDetailsObject,
18
+ getBrowserToRun,
19
+ getScreenToScan,
20
+ getClonedProfilesWithRandomToken,
21
+ deleteClonedProfiles,
22
+ } from './constants/common.js';
23
+ import questions from './constants/questions.js';
24
+ import combineRun from './combine.js';
25
+ import { BrowserTypes, RuleFlags, ScannerTypes } from './constants/constants.js';
26
+
27
+ export type Answers = {
28
+ headless: boolean;
29
+ deviceChosen: string;
30
+ customDevice: string;
31
+ viewportWidth: number;
32
+ browserToRun: BrowserTypes;
33
+ scanner: ScannerTypes;
34
+ url: string;
35
+ clonedBrowserDataDir: string;
36
+ playwrightDeviceDetailsObject: object;
37
+ nameEmail: string;
38
+ fileTypes: string;
39
+ metadata: string;
40
+ maxpages: number;
41
+ strategy: string;
42
+ isLocalFileScan: boolean;
43
+ finalUrl: string;
44
+ customFlowLabel: string;
45
+ specifiedMaxConcurrency: number;
46
+ blacklistedPatternsFilename: string;
47
+ additional: string;
48
+ followRobots: boolean;
49
+ header: string;
50
+ safeMode: boolean;
51
+ exportDirectory: string;
52
+ zip: string;
53
+ ruleset: RuleFlags[];
54
+ };
55
+
56
+ export type Data = {
57
+ type: ScannerTypes;
58
+ url: string;
59
+ entryUrl: string;
60
+ isHeadless: boolean;
61
+ deviceChosen: string;
62
+ customDevice: string;
63
+ viewportWidth: number;
64
+ playwrightDeviceDetailsObject: object;
65
+ maxRequestsPerCrawl: number;
66
+ strategy: EnqueueStrategy;
67
+ isLocalFileScan: boolean;
68
+ browser: string;
69
+ nameEmail: string;
70
+ customFlowLabel: string;
71
+ specifiedMaxConcurrency: number;
72
+ randomToken: string;
73
+ fileTypes: string;
74
+ blacklistedPatternsFilename: string;
75
+ includeScreenshots: boolean;
76
+ metadata: string;
77
+ followRobots: boolean;
78
+ extraHTTPHeaders: Record<string, string>;
79
+ safeMode: boolean;
80
+ userDataDirectory?: string;
81
+ zip?: string;
82
+ ruleset: RuleFlags[];
83
+ };
84
+
85
+ const userData = getUserDataTxt();
86
+
87
+ const runScan = async (answers: Answers) => {
88
+ const screenToScan = getScreenToScan(
89
+ answers.deviceChosen,
90
+ answers.customDevice,
91
+ answers.viewportWidth,
92
+ );
93
+ answers.playwrightDeviceDetailsObject = getPlaywrightDeviceDetailsObject(
94
+ answers.deviceChosen,
95
+ answers.customDevice,
96
+ answers.viewportWidth,
97
+ );
98
+ const { browserToRun } = getBrowserToRun(BrowserTypes.CHROME);
99
+ deleteClonedProfiles(browserToRun);
100
+ answers.browserToRun = browserToRun;
101
+
102
+ if (!answers.nameEmail) {
103
+ answers.nameEmail = `${userData.name}:${userData.email}`;
104
+ }
105
+
106
+ answers.fileTypes = 'html-only';
107
+ answers.metadata = '{}';
108
+
109
+ const data: Data = await prepareData(answers);
110
+ data.userDataDirectory = getClonedProfilesWithRandomToken(data.browser, data.randomToken);
111
+
112
+ setHeadlessMode(data.browser, data.isHeadless);
113
+ printMessage(['Scanning website...'], messageOptions);
114
+
115
+ await combineRun(data, screenToScan);
116
+
117
+ // Delete cloned directory
118
+ deleteClonedProfiles(data.browser);
119
+
120
+ // Delete dataset and request queues
121
+ cleanUp(data.randomToken);
122
+
123
+ process.exit(0);
124
+ };
125
+
126
+ if (userData) {
127
+ printMessage(
128
+ [
129
+ `Oobee (ver ${getVersion()})`,
130
+ 'We recommend using Chrome browser for the best experience.',
131
+ '',
132
+ `Welcome back ${userData.name}!`,
133
+ `(Refer to readme.txt on how to change your profile)`,
134
+ ],
135
+ {
136
+ // Note that the color is based on kleur NPM package
137
+ border: true,
138
+ borderColor: 'magenta',
139
+ },
140
+ );
141
+
142
+ inquirer.prompt(questions).then(async answers => {
143
+ await runScan(answers);
144
+ });
145
+ } else {
146
+ printMessage(
147
+ [`Oobee (ver ${getVersion()})`, 'We recommend using Chrome browser for the best experience.'],
148
+ {
149
+ // Note that the color is based on kleur NPM package
150
+ border: true,
151
+ borderColor: 'magenta',
152
+ },
153
+ );
154
+
155
+ printMessage(
156
+ [
157
+ `To personalise your experience, we will be collecting your name, email address and app usage data.`,
158
+ `Your information fully complies with GovTech's Privacy Policy.`,
159
+ ],
160
+ {
161
+ border: false,
162
+ },
163
+ );
164
+
165
+ inquirer.prompt(questions).then(async answers => {
166
+ const { name, email } = answers;
167
+ answers.nameEmail = `${name}:${email}`;
168
+ await writeToUserDataTxt('name', name);
169
+ await writeToUserDataTxt('email', email);
170
+
171
+ await runScan(answers);
172
+ });
173
+ }
package/src/logs.ts ADDED
@@ -0,0 +1,66 @@
1
+ /* eslint-disable no-console */
2
+ /* eslint-disable no-shadow */
3
+ import { createLogger, format, transports } from 'winston';
4
+ import { guiInfoStatusTypes } from './constants/constants.js';
5
+ import { urlWithoutAuth } from './constants/common.js';
6
+
7
+ const { combine, timestamp, printf } = format;
8
+
9
+ // Sample output
10
+ // {"timestamp":"2020-11-25 17:29:07","level":"error","message":"hello world"}
11
+ const logFormat = printf(({ timestamp, level, message }) => {
12
+ const log = {
13
+ timestamp: `${timestamp}`,
14
+ level: `${level}`,
15
+ message: `${message}`,
16
+ };
17
+
18
+ return JSON.stringify(log);
19
+ });
20
+
21
+ // transport: storage device for logs
22
+ // Enabled for console and storing into files; Files are overwritten each time
23
+ // All logs in combined.txt, error in errors.txt
24
+
25
+ const consoleLogger = createLogger({
26
+ format: combine(timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), logFormat),
27
+ transports: [new transports.Console()],
28
+ });
29
+
30
+ // No display in consoles, this will mostly be used within the interactive script to avoid disrupting the flow
31
+ // Also used in common functions to not link internal information
32
+ // if running from mass scanner, log out errors in console
33
+ const silentLogger = createLogger({
34
+ format: combine(timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), logFormat),
35
+ transports: [
36
+ process.env.OOBEE_VERBOSE
37
+ ? new transports.Console({ handleExceptions: true })
38
+ : new transports.File({ filename: 'errors.txt', level: 'warn', handleExceptions: true }),
39
+ ].filter(Boolean),
40
+ });
41
+
42
+ // guiInfoLogger feeds the gui information via console log and is mainly used for scanning process
43
+ export const guiInfoLog = (status, data) => {
44
+ if (process.env.RUNNING_FROM_PH_GUI || process.env.OOBEE_VERBOSE) {
45
+ switch (status) {
46
+ case guiInfoStatusTypes.COMPLETED:
47
+ console.log('Scan completed');
48
+ break;
49
+ case guiInfoStatusTypes.SCANNED:
50
+ case guiInfoStatusTypes.SKIPPED:
51
+ case guiInfoStatusTypes.ERROR:
52
+ case guiInfoStatusTypes.DUPLICATE:
53
+ console.log(
54
+ `crawling::${data.numScanned || 0}::${status}::${
55
+ urlWithoutAuth(data.urlScanned) || 'no url provided'
56
+ }`,
57
+ );
58
+ break;
59
+ default:
60
+ console.log(`Status provided to gui info log not recognized: ${status}`);
61
+ break;
62
+ }
63
+ }
64
+ };
65
+
66
+ export { logFormat, consoleLogger, silentLogger };