@govtechsg/oobee 0.10.69 → 0.10.72

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 (94) hide show
  1. package/DETAILS.md +0 -1
  2. package/README.md +13 -1
  3. package/S3_UPLOAD_README.md +172 -0
  4. package/dev/runGenerateJustHtmlReport.ts +25 -0
  5. package/package.json +4 -2
  6. package/src/combine.ts +71 -14
  7. package/src/constants/common.ts +89 -91
  8. package/src/constants/constants.ts +535 -60
  9. package/src/crawlers/crawlDomain.ts +313 -305
  10. package/src/crawlers/crawlIntelligentSitemap.ts +24 -18
  11. package/src/crawlers/crawlLocalFile.ts +31 -25
  12. package/src/crawlers/crawlSitemap.ts +264 -253
  13. package/src/crawlers/custom/utils.ts +809 -119
  14. package/src/crawlers/guards/urlGuard.ts +77 -0
  15. package/src/crawlers/runCustom.ts +32 -4
  16. package/src/generateHtmlReport.ts +224 -0
  17. package/src/mergeAxeResults.ts +94 -44
  18. package/src/runGenerateJustHtmlReport.ts +20 -0
  19. package/src/services/s3Uploader.ts +184 -0
  20. package/src/static/ejs/partials/components/allIssues/AllIssues.ejs +9 -0
  21. package/src/static/ejs/partials/components/allIssues/CategoryBadges.ejs +82 -0
  22. package/src/static/ejs/partials/components/allIssues/FilterBar.ejs +33 -0
  23. package/src/static/ejs/partials/components/allIssues/IssuesTable.ejs +41 -0
  24. package/src/static/ejs/partials/components/header/SiteInfo.ejs +119 -0
  25. package/src/static/ejs/partials/components/header/aboutScanModal/AboutScanModal.ejs +15 -0
  26. package/src/static/ejs/partials/components/header/aboutScanModal/ScanConfiguration.ejs +44 -0
  27. package/src/static/ejs/partials/components/header/aboutScanModal/ScanDetails.ejs +142 -0
  28. package/src/static/ejs/partials/components/prioritiseIssues/IssueDetailCard.ejs +36 -0
  29. package/src/static/ejs/partials/components/prioritiseIssues/PrioritiseIssues.ejs +47 -0
  30. package/src/static/ejs/partials/components/ruleModal/ruleOffcanvas.ejs +196 -0
  31. package/src/static/ejs/partials/components/scannedPagesSegmentedTabs.ejs +48 -0
  32. package/src/static/ejs/partials/components/shared/InfoAlert.ejs +3 -0
  33. package/src/static/ejs/partials/components/{topFive.ejs → topTen.ejs} +2 -2
  34. package/src/static/ejs/partials/components/wcagCompliance/FailedCriteria.ejs +47 -0
  35. package/src/static/ejs/partials/components/wcagCompliance/WcagCompliance.ejs +16 -0
  36. package/src/static/ejs/partials/components/wcagCompliance/WcagGaugeBar.ejs +16 -0
  37. package/src/static/ejs/partials/components/wcagCoverageDetails.ejs +18 -0
  38. package/src/static/ejs/partials/footer.ejs +1 -1
  39. package/src/static/ejs/partials/header.ejs +7 -223
  40. package/src/static/ejs/partials/main.ejs +12 -23
  41. package/src/static/ejs/partials/scripts/allIssues/AllIssues.ejs +376 -0
  42. package/src/static/ejs/partials/scripts/categorySummary.ejs +1 -1
  43. package/src/static/ejs/partials/scripts/header/SiteInfo.ejs +44 -0
  44. package/src/static/ejs/partials/scripts/header/aboutScanModal/AboutScanModal.ejs +51 -0
  45. package/src/static/ejs/partials/scripts/header/aboutScanModal/ScanConfiguration.ejs +127 -0
  46. package/src/static/ejs/partials/scripts/header/aboutScanModal/ScanDetails.ejs +60 -0
  47. package/src/static/ejs/partials/scripts/prioritiseIssues/IssueDetailCard.ejs +137 -0
  48. package/src/static/ejs/partials/scripts/prioritiseIssues/PrioritiseIssues.ejs +214 -0
  49. package/src/static/ejs/partials/scripts/prioritiseIssues/wcagSvgMap.ejs +861 -0
  50. package/src/static/ejs/partials/scripts/ruleModal/constants.ejs +949 -0
  51. package/src/static/ejs/partials/scripts/ruleModal/itemCardRenderer.ejs +352 -0
  52. package/src/static/ejs/partials/scripts/ruleModal/pageAccordionBuilder.ejs +468 -0
  53. package/src/static/ejs/partials/scripts/ruleModal/ruleOffcanvas.ejs +306 -0
  54. package/src/static/ejs/partials/scripts/ruleModal/utilities.ejs +483 -0
  55. package/src/static/ejs/partials/scripts/scannedPagesSegmentedTabs.ejs +35 -0
  56. package/src/static/ejs/partials/scripts/screenshotLightbox.ejs +61 -57
  57. package/src/static/ejs/partials/scripts/topTen.ejs +61 -0
  58. package/src/static/ejs/partials/scripts/utils.ejs +15 -0
  59. package/src/static/ejs/partials/scripts/wcagCompliance/FailedCriteria.ejs +103 -0
  60. package/src/static/ejs/partials/scripts/wcagCompliance/WcagGaugeBar.ejs +47 -0
  61. package/src/static/ejs/partials/scripts/wcagCompliance.ejs +15 -0
  62. package/src/static/ejs/partials/scripts/wcagCoverageDetails.ejs +75 -0
  63. package/src/static/ejs/partials/styles/allIssues/AllIssues.ejs +384 -0
  64. package/src/static/ejs/partials/styles/bootstrap.ejs +17 -1
  65. package/src/static/ejs/partials/styles/header/SiteInfo.ejs +121 -0
  66. package/src/static/ejs/partials/styles/header/aboutScanModal/AboutScanModal.ejs +82 -0
  67. package/src/static/ejs/partials/styles/header/aboutScanModal/ScanConfiguration.ejs +50 -0
  68. package/src/static/ejs/partials/styles/header/aboutScanModal/ScanDetails.ejs +149 -0
  69. package/src/static/ejs/partials/styles/header.ejs +7 -0
  70. package/src/static/ejs/partials/styles/prioritiseIssues/IssueDetailCard.ejs +141 -0
  71. package/src/static/ejs/partials/styles/prioritiseIssues/PrioritiseIssues.ejs +204 -0
  72. package/src/static/ejs/partials/styles/ruleModal/ruleOffcanvas.ejs +456 -0
  73. package/src/static/ejs/partials/styles/scannedPagesSegmentedTabs.ejs +46 -0
  74. package/src/static/ejs/partials/styles/shared/InfoAlert.ejs +12 -0
  75. package/src/static/ejs/partials/styles/styles.ejs +198 -470
  76. package/src/static/ejs/partials/styles/topTenCard.ejs +44 -0
  77. package/src/static/ejs/partials/styles/wcagCompliance/FailedCriteria.ejs +59 -0
  78. package/src/static/ejs/partials/styles/wcagCompliance/WcagGaugeBar.ejs +62 -0
  79. package/src/static/ejs/partials/styles/wcagCompliance.ejs +36 -0
  80. package/src/static/ejs/partials/styles/wcagCoverageDetails.ejs +33 -0
  81. package/src/static/ejs/report.ejs +42 -259
  82. package/src/static/ejs/summary.ejs +1 -1
  83. package/src/utils.ts +30 -0
  84. package/src/static/ejs/partials/components/categorySelector.ejs +0 -4
  85. package/src/static/ejs/partials/components/categorySelectorDropdown.ejs +0 -57
  86. package/src/static/ejs/partials/components/pagesScannedModal.ejs +0 -70
  87. package/src/static/ejs/partials/components/reportSearch.ejs +0 -47
  88. package/src/static/ejs/partials/components/ruleOffcanvas.ejs +0 -105
  89. package/src/static/ejs/partials/components/scanAbout.ejs +0 -328
  90. package/src/static/ejs/partials/components/wcagCompliance.ejs +0 -52
  91. package/src/static/ejs/partials/scripts/categorySelectorDropdownScript.ejs +0 -190
  92. package/src/static/ejs/partials/scripts/reportSearch.ejs +0 -287
  93. package/src/static/ejs/partials/scripts/ruleOffcanvas.ejs +0 -804
  94. package/src/static/ejs/partials/scripts/scanAboutScript.ejs +0 -38
package/DETAILS.md CHANGED
@@ -168,7 +168,6 @@ Note: Level AAA are disabled by default. Please specify `enable-wcag-aaa` in ru
168
168
  | Issue ID | Issue Description | Severity |
169
169
  | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
170
170
  | accesskeys | Ensures every accesskey attribute value is unique | Good to Fix |
171
- | aria-allowed-role | Ensures role attribute has an appropriate value for the element | Good to Fix |
172
171
  | aria-dialog-name | Ensures every ARIA dialog and alertdialog node has an accessible name | Good to Fix |
173
172
  | aria-text | Ensures role="text" is used on elements with no focusable descendants | Good to Fix |
174
173
  | aria-treeitem-name | Ensures every ARIA treeitem node has an accessible name | Good to Fix |
package/README.md CHANGED
@@ -571,7 +571,7 @@ The following URL and file validation error codes are provided to troubleshoot t
571
571
  | 11 | invalidUrl | Invalid URL. Please check and try again. | • Ensure the URL starts with `http://` or `https://`.<br>• Check for typos in the URL. |
572
572
  | 12 | cannotBeResolved | URL cannot be accessed. Please verify whether the website exists. | • Confirm the domain name is correct.<br>• Check DNS resolution with `ping` or `nslookup`.<br>• Ensure the site is publicly accessible (not behind VPN/firewall). |
573
573
  | 14 | systemError | Something went wrong when verifying the URL. Please try again in a few minutes. If this issue persists, please contact the Oobee team. | • Retry after a few minutes.<br>• Check internet connection.<br>• If persistent, report as a system issue. |
574
- | 15 | notASitemap | Invalid sitemap URL format. Please enter a valid sitemap URL ending with .XML e.g. https://www.example.com/sitemap.xml. | • Ensure the URL points to a valid XML sitemap.<br>• View [Examples of sitemaps sitemaps.org - Protocol](https://www.sitemaps.org/protocol.html)<br>• Test the URL in a browser to confirm it returns XML. |
574
+ | 15 | notASitemap | Invalid sitemap URL format. Please enter a valid sitemap URL ending with .XML or .TXT e.g. https://www.example.com/sitemap.xml. | • Ensure the URL points to a valid XML sitemap.<br>• View [Examples of sitemaps sitemaps.org - Protocol](https://www.sitemaps.org/protocol.html)<br>• Test the URL in a browser to confirm it returns XML. |
575
575
  | 16 | unauthorised | Login required. Please enter your credentials and try again. | • Check if the site requires username/password.<br>• Provide credentials in Oobee if supported. |
576
576
  | 17 | browserError | Incompatible browser. Please ensure you are using Chrome or Edge browser. | • Install the latest version of Chrome or Edge.|
577
577
  | 18 | sslProtocolError | SSL certificate error. Please check the SSL configuration of your website and try again. | • Verify SSL certificate validity (not expired, issued by trusted CA).<br>• Check for mismatched TLS versions or cipher issues.<br>• Use an SSL checker tool (e.g., Qualys SSL Labs). |
@@ -666,3 +666,15 @@ We recommend looking at our **Technology Stack** to understand the usage of each
666
666
  Oobee uses third-party open-source tools that may be downloaded over the Internet during the installation process of Oobee. Users should be aware of the libraries used by examining `package.json`.
667
667
 
668
668
  Oobee may send information to the website or URL where the user chooses to initiate a Oobee scan. Limited user information such as e-mail address, name, and basic analytics is collected for the purpose of knowing our usage patterns better.
669
+
670
+ ## Development and Testing Helpers
671
+
672
+ Useful helpers for Oobee developers
673
+ ### Generate new HTML
674
+ When working on EJS (HTML, CSS, JS), use the following helper script to test your changes so that you do not need to run a full scan to get the new report in results directory.
675
+
676
+ It uses the existing report *.json files for the embedded HTML dataset.
677
+
678
+ ```
679
+ npx tsx dev/runGenerateJustHtmlReport.ts results/<report directory>
680
+ ```
@@ -0,0 +1,172 @@
1
+ # S3 Upload Integration
2
+
3
+ This document explains how to use the S3 upload feature in the Oobee engine.
4
+
5
+ ## Overview
6
+
7
+ The Oobee engine can now automatically upload scan results to Amazon S3 after completing a scan. This enables integration with cloud-based workflows and event-driven architectures:
8
+
9
+ 1. Oobee runs and generates scan results locally
10
+ 2. Results are automatically uploaded to S3 with metadata
11
+ 3. S3 events can trigger downstream processing (e.g., serverless functions)
12
+ 4. External systems can process results and update databases, send notifications, etc.
13
+
14
+ ## Environment Variables
15
+
16
+ To enable S3 upload, set the following environment variables before running Oobee:
17
+
18
+ ### Required Variables
19
+
20
+ ```bash
21
+ # AWS Configuration
22
+ export AWS_REGION="ap-southeast-1" # AWS region
23
+ export AWS_ACCESS_KEY_ID="your-access-key" # AWS credentials
24
+ export AWS_SECRET_ACCESS_KEY="your-secret-key" # AWS credentials
25
+ export S3_BUCKET_NAME="oobee-scan-results" # S3 bucket name
26
+
27
+ # Scan Metadata (provided by your orchestration system)
28
+ export OOBEE_SCAN_ID="uuid-scan-id" # Unique scan identifier
29
+ export OOBEE_USER_ID="uuid-user-id" # User identifier
30
+ export OOBEE_USER_EMAIL="user@example.com" # User email
31
+
32
+ # Optional Variables
33
+ export CLOUDFRONT_BASE_URL="https://cdn.example.com" # CloudFront distribution URL
34
+ ```
35
+
36
+ ### S3 Upload Path
37
+
38
+ Files will be uploaded to: `s3://{bucket}/users/{userId}/scans/{scanId}/`
39
+
40
+ Example: `s3://oobee-scan-results/users/550e8400-e29b-41d4-a716-446655440000/scans/650e8400-e29b-41d4-a716-446655440001/`
41
+
42
+ ## Usage
43
+
44
+ ### 1. Automated Workflow (Containerized/Orchestrated)
45
+
46
+ When running Oobee in a containerized or orchestrated environment, pass the required environment variables to the container:
47
+
48
+ ```bash
49
+ # Docker example
50
+ docker run -e OOBEE_SCAN_ID="650e8400-e29b-41d4-a716-446655440001" \
51
+ -e OOBEE_USER_ID="550e8400-e29b-41d4-a716-446655440000" \
52
+ -e OOBEE_USER_EMAIL="user@example.com" \
53
+ -e S3_BUCKET_NAME="oobee-scan-results" \
54
+ -e AWS_REGION="ap-southeast-1" \
55
+ oobee-engine:latest
56
+
57
+ # Kubernetes example
58
+ env:
59
+ - name: OOBEE_SCAN_ID
60
+ value: "650e8400-e29b-41d4-a716-446655440001"
61
+ - name: OOBEE_USER_ID
62
+ value: "550e8400-e29b-41d4-a716-446655440000"
63
+ - name: OOBEE_USER_EMAIL
64
+ value: "user@example.com"
65
+ - name: S3_BUCKET_NAME
66
+ value: "oobee-scan-results"
67
+ - name: AWS_REGION
68
+ value: "ap-southeast-1"
69
+ ```
70
+
71
+ ### 2. From CLI (Manual Testing)
72
+
73
+ ```bash
74
+ # Set environment variables
75
+ export AWS_REGION="ap-southeast-1"
76
+ export AWS_ACCESS_KEY_ID="your-key"
77
+ export AWS_SECRET_ACCESS_KEY="your-secret"
78
+ ### 3. Without S3 Upload (Default Behavior)
79
+
80
+ If environment variables are not set, Oobee works as before:
81
+ - Results are saved locally only
82
+ - No S3 upload occurs
83
+ - No errors (logs informational message)
84
+
85
+ ```bash
86
+ # Run normally without S3 variables
87
+ npm run cli -- -c website -u https://example.com
88
+ ```
89
+ If environment variables are not set, Oobee will work as before:
90
+ - Results are saved locally
91
+ - No S3 upload occurs
92
+ - No errors or warnings (except info log)
93
+
94
+ ```bash
95
+ # Just run normally without S3 variables
96
+ npm run cli -- -c website -u https://example.com
97
+ ```
98
+
99
+ ## Implementation Details
100
+
101
+ ### Files Uploaded
102
+
103
+ The S3 uploader uploads all files from the results directory, but only tracks these file types in the response:
104
+ - `.html` - HTML reports
105
+ - `.csv` - CSV data files
106
+ - `.pdf` - PDF reports
107
+ - `.zip` - Zipped scan results
108
+
109
+ All files are uploaded with S3 metadata containing:
110
+ - `scanid` - Scan ID
111
+ - `userid` - User ID
112
+ - `useremail` - User email
113
+
114
+ ### S3 Metadata
115
+
116
+ Each uploaded file includes metadata that the S3 Lambda processor uses:
117
+
118
+ ```javascript
119
+ {
120
+ "scanid": "650e8400-e29b-41d4-a716-446655440001",
121
+ "userid": "550e8400-e29b-41d4-a716-446655440000",
122
+ "useremail": "user@example.com",
123
+ }
124
+ ```
125
+
126
+ ### Error Handling
127
+
128
+ - If S3 upload fails, the scan continues normally
129
+ - Error is logged but doesn't fail the scan
130
+ - Results are still available locally
131
+ - User can retry upload manually if needed
132
+
133
+ ### Key Functions
134
+
135
+ #### `isS3UploadEnabled()`
136
+ Checks if all required environment variables are set.
137
+
138
+ #### `getS3MetadataFromEnv()`
139
+ Extracts scan metadata from environment variables.
140
+
141
+ #### `getS3UploadPrefix()`
142
+ Generates the S3 path: `users/{userId}/scans/{scanId}`
143
+
144
+ #### `uploadFolderToS3(localPath, s3Prefix, metadata)`
145
+ Uploads entire results folder to S3 with metadata.
146
+
147
+ ## Testing
148
+
149
+ ### Local Testing
150
+
151
+ 1. Create a test S3 bucket:
152
+ ```bash
153
+ aws s3 mb s3://oobee-test-scan-results
154
+ ```
155
+
156
+ 2. Set test environment variables:
157
+ ```bash
158
+ export S3_BUCKET_NAME="oobee-test-scan-results"
159
+ export OOBEE_SCAN_ID="test-$(date +%s)"
160
+ export OOBEE_USER_ID="test-user-123"
161
+ export OOBEE_USER_EMAIL="test@example.com"
162
+ ```
163
+
164
+ 3. Run a test scan:
165
+ ```bash
166
+ npm run cli -- -c website -u https://example.com -p 5
167
+ ```
168
+
169
+ 4. Verify upload:
170
+ ```bash
171
+ aws s3 ls s3://oobee-test-scan-results/users/test-user-123/scans/
172
+ ```
@@ -0,0 +1,25 @@
1
+ /*
2
+ For developemnt and testing purposes only. Run the report generator
3
+ on an existing results directory.
4
+ */
5
+
6
+ import path from 'path';
7
+ import { generateHtmlReport } from '../src/generateHtmlReport.js';
8
+
9
+ async function main() {
10
+ const dirArg = process.argv[2];
11
+
12
+ if (!dirArg) {
13
+ console.error('Usage: npx tsx src/dev-generate-from-existing.ts <results-dir>');
14
+ process.exit(1);
15
+ }
16
+
17
+ const resultDir = path.resolve(process.cwd(), dirArg);
18
+ const out = await generateHtmlReport(resultDir);
19
+ console.log('\nOpen:', out);
20
+ }
21
+
22
+ main().catch((e) => {
23
+ console.error(e);
24
+ process.exit(1);
25
+ });
package/package.json CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "name": "@govtechsg/oobee",
3
3
  "main": "dist/npmIndex.js",
4
- "version": "0.10.69",
4
+ "version": "0.10.72",
5
5
  "type": "module",
6
6
  "author": "Government Technology Agency <info@tech.gov.sg>",
7
7
  "dependencies": {
8
+ "@aws-sdk/client-s3": "^3.948.0",
8
9
  "@json2csv/node": "^7.0.3",
9
10
  "@napi-rs/canvas": "^0.1.53",
10
11
  "@sentry/node": "^9.13.0",
12
+ "@types/aws-sdk": "^0.0.42",
11
13
  "axe-core": "^4.10.3",
12
14
  "axios": "^1.8.2",
13
15
  "base64-stream": "^1.0.0",
@@ -16,7 +18,7 @@
16
18
  "ejs": "^3.1.9",
17
19
  "file-type": "^19.5.0",
18
20
  "fs-extra": "^11.2.0",
19
- "glob": "^10.3.10",
21
+ "glob": "^11.1.0",
20
22
  "https": "^1.0.0",
21
23
  "inquirer": "^9.2.12",
22
24
  "jsdom": "^21.1.2",
package/src/combine.ts CHANGED
@@ -5,13 +5,19 @@ import crawlDomain from './crawlers/crawlDomain.js';
5
5
  import crawlLocalFile from './crawlers/crawlLocalFile.js';
6
6
  import crawlIntelligentSitemap from './crawlers/crawlIntelligentSitemap.js';
7
7
  import generateArtifacts from './mergeAxeResults.js';
8
- import { getHost, createAndUpdateResultsFolders, cleanUpAndExit } from './utils.js';
8
+ import { getHost, createAndUpdateResultsFolders, cleanUpAndExit, getStoragePath } from './utils.js';
9
9
  import { ScannerTypes, UrlsCrawled } from './constants/constants.js';
10
10
  import { getBlackListedPatterns, submitForm } from './constants/common.js';
11
11
  import { consoleLogger, silentLogger } from './logs.js';
12
12
  import runCustom from './crawlers/runCustom.js';
13
13
  import { alertMessageOptions } from './constants/cliFunctions.js';
14
14
  import { Data } from './index.js';
15
+ import {
16
+ isS3UploadEnabled,
17
+ getS3MetadataFromEnv,
18
+ getS3UploadPrefix,
19
+ uploadFolderToS3,
20
+ } from './services/s3Uploader.js';
15
21
 
16
22
  // Class exports
17
23
  export class ViewportSettingsClass {
@@ -61,18 +67,18 @@ const combineRun = async (details: Data, deviceToScan: string) => {
61
67
  zip,
62
68
  ruleset, // Enable custom checks, Enable WCAG AAA: if checked, = 'enable-wcag-aaa')
63
69
  generateJsonFiles,
64
- scanDuration
70
+ scanDuration,
65
71
  } = envDetails;
66
72
 
67
73
  process.env.CRAWLEE_LOG_LEVEL = 'ERROR';
68
74
  process.env.CRAWLEE_STORAGE_DIR = randomToken;
69
75
 
70
76
  if (process.env.CRAWLEE_SYSTEM_INFO_V2 === undefined) {
71
- // Set the environment variable to enable system info v2
72
- // Resolves issue with when wmic is not installed on Windows
77
+ // Set the environment variable to enable system info v2
78
+ // Resolves issue with when wmic is not installed on Windows
73
79
  process.env.CRAWLEE_SYSTEM_INFO_V2 = '1';
74
80
  }
75
-
81
+
76
82
  const host = type === ScannerTypes.SITEMAP || type === ScannerTypes.LOCALFILE ? '' : getHost(url);
77
83
 
78
84
  let blacklistedPatterns: string[] | null = null;
@@ -121,19 +127,26 @@ const combineRun = async (details: Data, deviceToScan: string) => {
121
127
  );
122
128
 
123
129
  let urlsCrawledObj: UrlsCrawled;
130
+ let uiCustomFlowLabel: string | undefined;
131
+ let durationExceeded = false;
132
+
124
133
  switch (type) {
125
134
  case ScannerTypes.CUSTOM:
126
- urlsCrawledObj = await runCustom(
135
+ const res = await runCustom(
127
136
  url,
128
137
  randomToken,
129
138
  viewportSettings,
130
139
  blacklistedPatterns,
131
140
  includeScreenshots,
141
+ customFlowLabel && customFlowLabel !== 'None' ? customFlowLabel : '',
132
142
  );
143
+
144
+ urlsCrawledObj = res.urlsCrawled;
145
+ uiCustomFlowLabel = res.customFlowLabel;
133
146
  break;
134
147
 
135
148
  case ScannerTypes.SITEMAP:
136
- urlsCrawledObj = await crawlSitemap({
149
+ const sitemapResult = await crawlSitemap({
137
150
  sitemapUrl: url,
138
151
  randomToken,
139
152
  host,
@@ -148,10 +161,12 @@ const combineRun = async (details: Data, deviceToScan: string) => {
148
161
  extraHTTPHeaders,
149
162
  scanDuration,
150
163
  });
164
+ urlsCrawledObj = sitemapResult.urlsCrawled;
165
+ durationExceeded = sitemapResult.durationExceeded;
151
166
  break;
152
167
 
153
168
  case ScannerTypes.LOCALFILE:
154
- urlsCrawledObj = await crawlLocalFile({
169
+ const localFileResult = await crawlLocalFile({
155
170
  url,
156
171
  randomToken,
157
172
  host,
@@ -166,10 +181,18 @@ const combineRun = async (details: Data, deviceToScan: string) => {
166
181
  extraHTTPHeaders,
167
182
  scanDuration,
168
183
  });
184
+ if (localFileResult) {
185
+ if ('urlsCrawled' in localFileResult) {
186
+ urlsCrawledObj = localFileResult.urlsCrawled;
187
+ durationExceeded = localFileResult.durationExceeded;
188
+ } else {
189
+ urlsCrawledObj = localFileResult;
190
+ }
191
+ }
169
192
  break;
170
193
 
171
194
  case ScannerTypes.INTELLIGENT:
172
- urlsCrawledObj = await crawlIntelligentSitemap(
195
+ const intelligentResult = await crawlIntelligentSitemap(
173
196
  url,
174
197
  randomToken,
175
198
  host,
@@ -185,12 +208,14 @@ const combineRun = async (details: Data, deviceToScan: string) => {
185
208
  followRobots,
186
209
  extraHTTPHeaders,
187
210
  safeMode,
188
- scanDuration
211
+ scanDuration,
189
212
  );
213
+ urlsCrawledObj = intelligentResult.urlsCrawled;
214
+ durationExceeded = intelligentResult.durationExceeded;
190
215
  break;
191
216
 
192
217
  case ScannerTypes.WEBSITE:
193
- urlsCrawledObj = await crawlDomain({
218
+ const websiteResult = await crawlDomain({
194
219
  url,
195
220
  randomToken,
196
221
  host,
@@ -209,6 +234,8 @@ const combineRun = async (details: Data, deviceToScan: string) => {
209
234
  safeMode,
210
235
  ruleset,
211
236
  });
237
+ urlsCrawledObj = websiteResult.urlsCrawled;
238
+ durationExceeded = websiteResult.durationExceeded;
212
239
  break;
213
240
 
214
241
  default:
@@ -235,7 +262,9 @@ const combineRun = async (details: Data, deviceToScan: string) => {
235
262
  deviceToScan,
236
263
  urlsCrawledObj.scanned,
237
264
  pagesNotScanned,
238
- customFlowLabel,
265
+ uiCustomFlowLabel && uiCustomFlowLabel.length > 0
266
+ ? uiCustomFlowLabel
267
+ : customFlowLabel || 'None',
239
268
  undefined,
240
269
  scanDetails,
241
270
  zip,
@@ -243,6 +272,36 @@ const combineRun = async (details: Data, deviceToScan: string) => {
243
272
  );
244
273
  const [name, email] = nameEmail.split(':');
245
274
 
275
+ // Upload results to S3 if environment variables are set
276
+ if (isS3UploadEnabled()) {
277
+ const siteName = (urlsCrawledObj.scanned[0]?.pageTitle ?? '')
278
+ .replace(/^\d+\s*:\s*/, '')
279
+ .trim();
280
+ const scanMetadata = getS3MetadataFromEnv(siteName, durationExceeded);
281
+ const s3Prefix = getS3UploadPrefix();
282
+
283
+ if (scanMetadata && s3Prefix) {
284
+ try {
285
+ const storagePath = getStoragePath(randomToken);
286
+ consoleLogger.info('Starting S3 upload...');
287
+ consoleLogger.info(`Upload path: ${s3Prefix}`);
288
+
289
+ const uploadedFiles = await uploadFolderToS3(storagePath, s3Prefix, scanMetadata);
290
+
291
+ consoleLogger.info(`Successfully uploaded ${uploadedFiles.length} files to S3`);
292
+ } catch (error) {
293
+ const errorMessage = error instanceof Error ? error.message : String(error);
294
+ consoleLogger.error(`Failed to upload results to S3: ${errorMessage}`);
295
+ // Don't fail the scan if S3 upload fails
296
+ consoleLogger.warn('Continuing without S3 upload...');
297
+ }
298
+ } else {
299
+ consoleLogger.warn('S3 upload enabled but metadata/prefix not available');
300
+ }
301
+ } else {
302
+ consoleLogger.info('S3 upload not enabled (missing environment variables)');
303
+ }
304
+
246
305
  await submitForm(
247
306
  browser,
248
307
  userDataDirectory,
@@ -258,13 +317,11 @@ const combineRun = async (details: Data, deviceToScan: string) => {
258
317
  metadata,
259
318
  );
260
319
  } else {
261
-
262
320
  // No page were scanned because the URL loaded does not meet the crawler requirements
263
321
  printMessage([`No pages were scanned.`], alertMessageOptions);
264
322
  cleanUpAndExit(1, randomToken, true);
265
323
  }
266
324
  } else {
267
-
268
325
  // No page were scanned because the URL loaded does not meet the crawler requirements
269
326
  printMessage([`No pages were scanned.`], alertMessageOptions);
270
327
  cleanUpAndExit(1, randomToken, true);