@govtechsg/oobee 0.10.76 → 0.10.78-alpha1
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/.github/workflows/publish.yml +8 -1
- package/INTEGRATION.md +50 -3
- package/dist/cli.js +252 -0
- package/dist/combine.js +221 -0
- package/dist/constants/cliFunctions.js +306 -0
- package/dist/constants/common.js +1669 -0
- package/dist/constants/constants.js +913 -0
- package/dist/constants/errorMeta.json +319 -0
- package/dist/constants/itemTypeDescription.js +7 -0
- package/dist/constants/oobeeAi.js +121 -0
- package/dist/constants/questions.js +151 -0
- package/dist/constants/sampleData.js +176 -0
- package/dist/crawlers/commonCrawlerFunc.js +428 -0
- package/dist/crawlers/crawlDomain.js +613 -0
- package/dist/crawlers/crawlIntelligentSitemap.js +135 -0
- package/dist/crawlers/crawlLocalFile.js +151 -0
- package/dist/crawlers/crawlSitemap.js +303 -0
- package/dist/crawlers/custom/escapeCssSelector.js +10 -0
- package/dist/crawlers/custom/evaluateAltText.js +11 -0
- package/dist/crawlers/custom/extractAndGradeText.js +44 -0
- package/dist/crawlers/custom/extractText.js +27 -0
- package/dist/crawlers/custom/findElementByCssSelector.js +36 -0
- package/dist/crawlers/custom/flagUnlabelledClickableElements.js +963 -0
- package/dist/crawlers/custom/framesCheck.js +37 -0
- package/dist/crawlers/custom/getAxeConfiguration.js +111 -0
- package/dist/crawlers/custom/gradeReadability.js +23 -0
- package/dist/crawlers/custom/utils.js +1024 -0
- package/dist/crawlers/custom/xPathToCss.js +147 -0
- package/dist/crawlers/guards/urlGuard.js +71 -0
- package/dist/crawlers/pdfScanFunc.js +276 -0
- package/dist/crawlers/runCustom.js +89 -0
- package/dist/exclusions.txt +7 -0
- package/dist/generateHtmlReport.js +144 -0
- package/dist/index.js +62 -0
- package/dist/logs.js +84 -0
- package/dist/mergeAxeResults.js +1588 -0
- package/dist/npmIndex.js +640 -0
- package/dist/proxyService.js +360 -0
- package/dist/runGenerateJustHtmlReport.js +16 -0
- package/dist/screenshotFunc/htmlScreenshotFunc.js +355 -0
- package/dist/screenshotFunc/pdfScreenshotFunc.js +645 -0
- package/dist/services/s3Uploader.js +127 -0
- package/dist/static/ejs/partials/components/allIssues/AllIssues.ejs +9 -0
- package/dist/static/ejs/partials/components/allIssues/CategoryBadges.ejs +82 -0
- package/dist/static/ejs/partials/components/allIssues/FilterBar.ejs +33 -0
- package/dist/static/ejs/partials/components/allIssues/IssuesTable.ejs +41 -0
- package/dist/static/ejs/partials/components/header/SiteInfo.ejs +119 -0
- package/dist/static/ejs/partials/components/header/aboutScanModal/AboutScanModal.ejs +15 -0
- package/dist/static/ejs/partials/components/header/aboutScanModal/ScanConfiguration.ejs +44 -0
- package/dist/static/ejs/partials/components/header/aboutScanModal/ScanDetails.ejs +142 -0
- package/dist/static/ejs/partials/components/prioritiseIssues/IssueDetailCard.ejs +36 -0
- package/dist/static/ejs/partials/components/prioritiseIssues/PrioritiseIssues.ejs +47 -0
- package/dist/static/ejs/partials/components/ruleModal/ruleOffcanvas.ejs +196 -0
- package/dist/static/ejs/partials/components/scannedPagesSegmentedTabs.ejs +48 -0
- package/dist/static/ejs/partials/components/screenshotLightbox.ejs +13 -0
- package/dist/static/ejs/partials/components/shared/InfoAlert.ejs +3 -0
- package/dist/static/ejs/partials/components/summaryScanAbout.ejs +141 -0
- package/dist/static/ejs/partials/components/summaryScanResults.ejs +16 -0
- package/dist/static/ejs/partials/components/summaryTable.ejs +20 -0
- package/dist/static/ejs/partials/components/summaryWcagCompliance.ejs +94 -0
- package/dist/static/ejs/partials/components/topTen.ejs +6 -0
- package/dist/static/ejs/partials/components/wcagCompliance/FailedCriteria.ejs +47 -0
- package/dist/static/ejs/partials/components/wcagCompliance/WcagCompliance.ejs +16 -0
- package/dist/static/ejs/partials/components/wcagCompliance/WcagGaugeBar.ejs +16 -0
- package/dist/static/ejs/partials/components/wcagCoverageDetails.ejs +18 -0
- package/dist/static/ejs/partials/footer.ejs +24 -0
- package/dist/static/ejs/partials/header.ejs +14 -0
- package/dist/static/ejs/partials/main.ejs +29 -0
- package/dist/static/ejs/partials/scripts/allIssues/AllIssues.ejs +376 -0
- package/dist/static/ejs/partials/scripts/bootstrap.ejs +8 -0
- package/dist/static/ejs/partials/scripts/categorySummary.ejs +141 -0
- package/dist/static/ejs/partials/scripts/decodeUnzipParse.ejs +3 -0
- package/dist/static/ejs/partials/scripts/header/SiteInfo.ejs +44 -0
- package/dist/static/ejs/partials/scripts/header/aboutScanModal/AboutScanModal.ejs +51 -0
- package/dist/static/ejs/partials/scripts/header/aboutScanModal/ScanConfiguration.ejs +127 -0
- package/dist/static/ejs/partials/scripts/header/aboutScanModal/ScanDetails.ejs +60 -0
- package/dist/static/ejs/partials/scripts/highlightjs.ejs +335 -0
- package/dist/static/ejs/partials/scripts/popper.ejs +7 -0
- package/dist/static/ejs/partials/scripts/prioritiseIssues/IssueDetailCard.ejs +137 -0
- package/dist/static/ejs/partials/scripts/prioritiseIssues/PrioritiseIssues.ejs +214 -0
- package/dist/static/ejs/partials/scripts/prioritiseIssues/wcagSvgMap.ejs +861 -0
- package/dist/static/ejs/partials/scripts/ruleModal/constants.ejs +957 -0
- package/dist/static/ejs/partials/scripts/ruleModal/itemCardRenderer.ejs +353 -0
- package/dist/static/ejs/partials/scripts/ruleModal/pageAccordionBuilder.ejs +468 -0
- package/dist/static/ejs/partials/scripts/ruleModal/ruleOffcanvas.ejs +306 -0
- package/dist/static/ejs/partials/scripts/ruleModal/utilities.ejs +483 -0
- package/dist/static/ejs/partials/scripts/scannedPagesSegmentedTabs.ejs +35 -0
- package/dist/static/ejs/partials/scripts/screenshotLightbox.ejs +75 -0
- package/dist/static/ejs/partials/scripts/summaryScanResults.ejs +14 -0
- package/dist/static/ejs/partials/scripts/summaryTable.ejs +78 -0
- package/dist/static/ejs/partials/scripts/topTen.ejs +61 -0
- package/dist/static/ejs/partials/scripts/utils.ejs +453 -0
- package/dist/static/ejs/partials/scripts/wcagCompliance/FailedCriteria.ejs +103 -0
- package/dist/static/ejs/partials/scripts/wcagCompliance/WcagGaugeBar.ejs +47 -0
- package/dist/static/ejs/partials/scripts/wcagCompliance.ejs +15 -0
- package/dist/static/ejs/partials/scripts/wcagCoverageDetails.ejs +75 -0
- package/dist/static/ejs/partials/styles/allIssues/AllIssues.ejs +384 -0
- package/dist/static/ejs/partials/styles/bootstrap.ejs +12391 -0
- package/dist/static/ejs/partials/styles/header/SiteInfo.ejs +121 -0
- package/dist/static/ejs/partials/styles/header/aboutScanModal/AboutScanModal.ejs +82 -0
- package/dist/static/ejs/partials/styles/header/aboutScanModal/ScanConfiguration.ejs +50 -0
- package/dist/static/ejs/partials/styles/header/aboutScanModal/ScanDetails.ejs +149 -0
- package/dist/static/ejs/partials/styles/header.ejs +7 -0
- package/dist/static/ejs/partials/styles/highlightjs.ejs +54 -0
- package/dist/static/ejs/partials/styles/prioritiseIssues/IssueDetailCard.ejs +141 -0
- package/dist/static/ejs/partials/styles/prioritiseIssues/PrioritiseIssues.ejs +204 -0
- package/dist/static/ejs/partials/styles/ruleModal/ruleOffcanvas.ejs +456 -0
- package/dist/static/ejs/partials/styles/scannedPagesSegmentedTabs.ejs +46 -0
- package/dist/static/ejs/partials/styles/shared/InfoAlert.ejs +12 -0
- package/dist/static/ejs/partials/styles/styles.ejs +1607 -0
- package/dist/static/ejs/partials/styles/summaryBootstrap.ejs +12458 -0
- package/dist/static/ejs/partials/styles/topTenCard.ejs +44 -0
- package/dist/static/ejs/partials/styles/wcagCompliance/FailedCriteria.ejs +59 -0
- package/dist/static/ejs/partials/styles/wcagCompliance/WcagGaugeBar.ejs +62 -0
- package/dist/static/ejs/partials/styles/wcagCompliance.ejs +36 -0
- package/dist/static/ejs/partials/styles/wcagCoverageDetails.ejs +33 -0
- package/dist/static/ejs/partials/summaryHeader.ejs +70 -0
- package/dist/static/ejs/partials/summaryMain.ejs +49 -0
- package/dist/static/ejs/report.ejs +226 -0
- package/dist/static/ejs/summary.ejs +47 -0
- package/dist/types/types.js +1 -0
- package/dist/utils.js +1070 -0
- package/examples/oobee-cypress-integration-js/cypress/support/e2e.js +36 -6
- package/examples/oobee-cypress-integration-js/cypress.config.js +45 -1
- package/examples/oobee-cypress-integration-ts/cypress.config.ts +47 -1
- package/examples/oobee-cypress-integration-ts/src/cypress/support/e2e.ts +36 -6
- package/examples/oobee-playwright-integration-js/oobee-playwright-demo.js +2 -1
- package/examples/oobee-playwright-integration-ts/src/oobee-playwright-demo.ts +2 -1
- package/examples/oobee-scan-html-demo.js +51 -0
- package/examples/oobee-scan-page-demo.js +40 -0
- package/package.json +9 -3
- package/src/constants/common.ts +2 -2
- package/src/constants/constants.ts +3 -1
- package/src/crawlers/crawlDomain.ts +1 -0
- package/src/crawlers/runCustom.ts +0 -1
- package/src/mergeAxeResults.ts +43 -22
- package/src/npmIndex.ts +500 -131
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
// getProxyInfo.ts
|
|
2
|
+
// Cross-platform proxy detector for Playwright/Chromium with PAC + credentials + macOS Keychain support.
|
|
3
|
+
//
|
|
4
|
+
// Windows: WinINET registry (HKCU → HKLM)
|
|
5
|
+
// macOS: env vars first, then `scutil --proxy` (reads per-protocol usernames/passwords;
|
|
6
|
+
// if password is missing, fetch it from Keychain via `/usr/bin/security`)
|
|
7
|
+
// Linux/others: env vars only
|
|
8
|
+
//
|
|
9
|
+
// Output precedence in proxyInfoToArgs():
|
|
10
|
+
// 1) pacUrl → ["--proxy-pac-url=<url>", ...bypass]
|
|
11
|
+
// 2) manual proxies → ["--proxy-server=...", ...bypass] (embeds creds if provided/available)
|
|
12
|
+
// 3) autoDetect → ["--proxy-auto-detect", ...bypass]
|
|
13
|
+
import os from 'os';
|
|
14
|
+
import fs from 'fs';
|
|
15
|
+
import path from 'path';
|
|
16
|
+
import { spawnSync } from 'child_process';
|
|
17
|
+
/* ============================ helpers ============================ */
|
|
18
|
+
function stripScheme(u) {
|
|
19
|
+
return u.replace(/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//, '');
|
|
20
|
+
}
|
|
21
|
+
function semiJoin(arr) {
|
|
22
|
+
if (!arr)
|
|
23
|
+
return undefined;
|
|
24
|
+
const cleaned = arr.map(s => s.trim()).filter(Boolean);
|
|
25
|
+
return cleaned.length ? cleaned.join(';') : undefined;
|
|
26
|
+
}
|
|
27
|
+
function readCredsFromEnv() {
|
|
28
|
+
const username = process.env.PROXY_USERNAME || process.env.HTTP_PROXY_USERNAME || undefined;
|
|
29
|
+
const password = process.env.PROXY_PASSWORD || process.env.HTTP_PROXY_PASSWORD || undefined;
|
|
30
|
+
return { username, password };
|
|
31
|
+
}
|
|
32
|
+
function hasUserinfo(v) {
|
|
33
|
+
return !!(v && /@/.test(v.split('/')[0]));
|
|
34
|
+
}
|
|
35
|
+
function pick(map, keys) {
|
|
36
|
+
for (const k of keys) {
|
|
37
|
+
const v = map[k];
|
|
38
|
+
if (v && String(v).trim().length)
|
|
39
|
+
return String(v).trim();
|
|
40
|
+
}
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
function extractHost(valueHostPort) {
|
|
44
|
+
// valueHostPort may be "user:pass@host:port" or "host:port"
|
|
45
|
+
const noUser = valueHostPort.includes('@') ? valueHostPort.split('@', 2)[1] : valueHostPort;
|
|
46
|
+
return noUser.split(':', 2)[0];
|
|
47
|
+
}
|
|
48
|
+
/* ============================ env (macOS + Linux) ============================ */
|
|
49
|
+
function parseEnvProxyCommon() {
|
|
50
|
+
const http = process.env.HTTP_PROXY || process.env.http_proxy || '';
|
|
51
|
+
const https = process.env.HTTPS_PROXY || process.env.https_proxy || '';
|
|
52
|
+
const socks = process.env.ALL_PROXY || process.env.all_proxy || '';
|
|
53
|
+
const noProxy = process.env.NO_PROXY || process.env.no_proxy || '';
|
|
54
|
+
const info = {};
|
|
55
|
+
if (http)
|
|
56
|
+
info.http = stripScheme(http);
|
|
57
|
+
if (https)
|
|
58
|
+
info.https = stripScheme(https);
|
|
59
|
+
if (socks)
|
|
60
|
+
info.socks = stripScheme(socks);
|
|
61
|
+
if (noProxy)
|
|
62
|
+
info.bypassList = semiJoin(noProxy.split(/[,;]/));
|
|
63
|
+
const { username, password } = readCredsFromEnv();
|
|
64
|
+
if (username && password) {
|
|
65
|
+
info.username = username;
|
|
66
|
+
info.password = password;
|
|
67
|
+
}
|
|
68
|
+
return (info.http || info.https || info.socks || info.bypassList) ? info : null;
|
|
69
|
+
}
|
|
70
|
+
/* ============================ macOS Keychain ============================ */
|
|
71
|
+
/**
|
|
72
|
+
* Try to read an Internet Password from macOS Keychain for a given host/account.
|
|
73
|
+
* We intentionally avoid passing any user-controlled strings; host/account come from scutil.
|
|
74
|
+
* Returns the password (raw) or undefined.
|
|
75
|
+
*/
|
|
76
|
+
export function keychainFindInternetPassword(host, account) {
|
|
77
|
+
console.log("Attempting to find internet proxy password in macOS keychain...");
|
|
78
|
+
// Only attempt on macOS, in an interactive session, or when explicitly allowed.
|
|
79
|
+
if (process.platform !== 'darwin')
|
|
80
|
+
return undefined;
|
|
81
|
+
const allow = process.stdin.isTTY || process.env.ENABLE_KEYCHAIN_LOOKUP === '1';
|
|
82
|
+
if (!allow)
|
|
83
|
+
return undefined;
|
|
84
|
+
const SECURITY_BIN = '/usr/bin/security';
|
|
85
|
+
const OUTPUT_LIMIT = 64 * 1024; // 64 KiB
|
|
86
|
+
// Verify absolute binary and realpath
|
|
87
|
+
try {
|
|
88
|
+
if (!fs.existsSync(SECURITY_BIN))
|
|
89
|
+
return undefined;
|
|
90
|
+
const real = fs.realpathSync(SECURITY_BIN);
|
|
91
|
+
if (real !== SECURITY_BIN)
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
// Minimal sanitized env (avoid proxy/env influence)
|
|
98
|
+
const env = {
|
|
99
|
+
PATH: '/usr/bin:/bin',
|
|
100
|
+
http_proxy: '', https_proxy: '', all_proxy: '', no_proxy: '',
|
|
101
|
+
HTTP_PROXY: '', HTTPS_PROXY: '', ALL_PROXY: '', NO_PROXY: '',
|
|
102
|
+
NODE_OPTIONS: '', NODE_PATH: '', DYLD_LIBRARY_PATH: '', LD_LIBRARY_PATH: '',
|
|
103
|
+
};
|
|
104
|
+
const baseArgs = ['find-internet-password', '-s', host, '-w'];
|
|
105
|
+
const args = account ? [...baseArgs, '-a', account] : baseArgs;
|
|
106
|
+
// No timeout: allow user to respond to Keychain prompt
|
|
107
|
+
const res = spawnSync(SECURITY_BIN, args, {
|
|
108
|
+
encoding: 'utf8',
|
|
109
|
+
windowsHide: true,
|
|
110
|
+
shell: false,
|
|
111
|
+
env,
|
|
112
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
113
|
+
});
|
|
114
|
+
if (!res.error && res.status === 0 && res.stdout) {
|
|
115
|
+
const out = res.stdout.slice(0, OUTPUT_LIMIT).replace(/\r?\n$/, '');
|
|
116
|
+
return out || undefined;
|
|
117
|
+
}
|
|
118
|
+
// Retry without account if first try used one
|
|
119
|
+
if (account) {
|
|
120
|
+
const retry = spawnSync(SECURITY_BIN, baseArgs, {
|
|
121
|
+
encoding: 'utf8',
|
|
122
|
+
windowsHide: true,
|
|
123
|
+
shell: false,
|
|
124
|
+
env,
|
|
125
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
126
|
+
});
|
|
127
|
+
if (!retry.error && retry.status === 0 && retry.stdout) {
|
|
128
|
+
const out = retry.stdout.slice(0, OUTPUT_LIMIT).replace(/\r?\n$/, '');
|
|
129
|
+
return out || undefined;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// Common Keychain errors you may see in retry.stderr:
|
|
133
|
+
// - "User interaction is not allowed." (Keychain locked / non-interactive / no UI permission)
|
|
134
|
+
// - "The specified item could not be found in the keychain."
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
/* ============================ macOS fallback (scutil) ============================ */
|
|
138
|
+
function parseMacScutil() {
|
|
139
|
+
const out = spawnSync('/usr/sbin/scutil', ['--proxy'], {
|
|
140
|
+
encoding: 'utf8',
|
|
141
|
+
windowsHide: true,
|
|
142
|
+
shell: false,
|
|
143
|
+
});
|
|
144
|
+
if (out.error || !out.stdout)
|
|
145
|
+
return null;
|
|
146
|
+
const map = {};
|
|
147
|
+
for (const line of out.stdout.split(/\r?\n/)) {
|
|
148
|
+
const m = line.match(/^\s*([A-Za-z0-9]+)\s*:\s*(.+?)\s*$/);
|
|
149
|
+
if (m)
|
|
150
|
+
map[m[1]] = m[2];
|
|
151
|
+
}
|
|
152
|
+
const info = {};
|
|
153
|
+
// PAC + autodetect
|
|
154
|
+
if (map['ProxyAutoConfigEnable'] === '1' && map['ProxyAutoConfigURLString']) {
|
|
155
|
+
info.pacUrl = map['ProxyAutoConfigURLString'].trim();
|
|
156
|
+
}
|
|
157
|
+
if (map['ProxyAutoDiscoveryEnable'] === '1')
|
|
158
|
+
info.autoDetect = true;
|
|
159
|
+
// Collect per-protocol creds (Apple keys vary by macOS version)
|
|
160
|
+
const httpUser = pick(map, ['HTTPProxyUsername', 'HTTPUser', 'HTTPUsername']);
|
|
161
|
+
let httpPass = pick(map, ['HTTPProxyPassword', 'HTTPPassword']);
|
|
162
|
+
const httpsUser = pick(map, ['HTTPSProxyUsername', 'HTTPSUser', 'HTTPSUsername']);
|
|
163
|
+
let httpsPass = pick(map, ['HTTPSProxyPassword', 'HTTPSPassword']);
|
|
164
|
+
const socksUser = pick(map, ['SOCKSProxyUsername', 'SOCKSUser', 'SOCKSUsername']);
|
|
165
|
+
let socksPass = pick(map, ['SOCKSProxyPassword', 'SOCKSPassword']);
|
|
166
|
+
// Manual proxies (always set host:port only; never include creds)
|
|
167
|
+
if (map['HTTPEnable'] === '1' && map['HTTPProxy'] && map['HTTPPort']) {
|
|
168
|
+
const hostPort = `${map['HTTPProxy']}:${map['HTTPPort']}`;
|
|
169
|
+
info.http = hostPort;
|
|
170
|
+
// If macOS has username but no password, try Keychain for this host/account
|
|
171
|
+
if (httpUser && !httpPass)
|
|
172
|
+
httpPass = keychainFindInternetPassword(extractHost(hostPort), httpUser);
|
|
173
|
+
}
|
|
174
|
+
if (map['HTTPSEnable'] === '1' && map['HTTPSProxy'] && map['HTTPSPort']) {
|
|
175
|
+
const hostPort = `${map['HTTPSProxy']}:${map['HTTPSPort']}`;
|
|
176
|
+
info.https = hostPort;
|
|
177
|
+
if (httpsUser && !httpsPass)
|
|
178
|
+
httpsPass = keychainFindInternetPassword(extractHost(hostPort), httpsUser);
|
|
179
|
+
}
|
|
180
|
+
if (map['SOCKSEnable'] === '1' && map['SOCKSProxy'] && map['SOCKSPort']) {
|
|
181
|
+
const hostPort = `${map['SOCKSProxy']}:${map['SOCKSPort']}`;
|
|
182
|
+
info.socks = hostPort;
|
|
183
|
+
if (socksUser && !socksPass)
|
|
184
|
+
socksPass = keychainFindInternetPassword(extractHost(hostPort), socksUser);
|
|
185
|
+
}
|
|
186
|
+
// Choose one set of creds to expose globally: prefer HTTP, else HTTPS, else SOCKS
|
|
187
|
+
// (Do not overwrite if env already provided username/password.)
|
|
188
|
+
if (!info.username && !info.password) {
|
|
189
|
+
if (httpUser && httpPass) {
|
|
190
|
+
info.username = httpUser;
|
|
191
|
+
info.password = httpPass;
|
|
192
|
+
}
|
|
193
|
+
else if (httpsUser && httpsPass) {
|
|
194
|
+
info.username = httpsUser;
|
|
195
|
+
info.password = httpsPass;
|
|
196
|
+
}
|
|
197
|
+
else if (socksUser && socksPass) {
|
|
198
|
+
info.username = socksUser;
|
|
199
|
+
info.password = socksPass;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// Bypass list
|
|
203
|
+
let exceptions = [];
|
|
204
|
+
if (map['ExceptionsList']) {
|
|
205
|
+
const quoted = [...map['ExceptionsList'].matchAll(/"([^"]+)"/g)].map(m => m[1]);
|
|
206
|
+
exceptions = quoted.length
|
|
207
|
+
? quoted
|
|
208
|
+
: map['ExceptionsList'].replace(/[<>{}()]/g, ' ')
|
|
209
|
+
.split(/[,\s]+/).map(s => s.trim()).filter(Boolean);
|
|
210
|
+
}
|
|
211
|
+
if (map['ExcludeSimpleHostnames'] === '1' && !exceptions.includes('<local>'))
|
|
212
|
+
exceptions.unshift('<local>');
|
|
213
|
+
const bypassList = semiJoin(exceptions);
|
|
214
|
+
if (bypassList)
|
|
215
|
+
info.bypassList = bypassList;
|
|
216
|
+
// If scutil did not provide creds anywhere, still allow env creds as global fallback
|
|
217
|
+
if ((!info.username || !info.password)) {
|
|
218
|
+
const envCreds = readCredsFromEnv();
|
|
219
|
+
if (envCreds.username && envCreds.password) {
|
|
220
|
+
info.username = info.username || envCreds.username;
|
|
221
|
+
info.password = info.password || envCreds.password;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return (info.pacUrl || info.http || info.https || info.socks || info.autoDetect || info.bypassList) ? info : null;
|
|
225
|
+
}
|
|
226
|
+
/* ============================ Windows (Registry only) ============================ */
|
|
227
|
+
function regExeCandidates() {
|
|
228
|
+
const root = process.env.SystemRoot || 'C:\\Windows';
|
|
229
|
+
const sys32 = path.join(root, 'System32', 'reg.exe');
|
|
230
|
+
const syswow64 = path.join(root, 'SysWOW64', 'reg.exe');
|
|
231
|
+
const sysnative = path.join(root, 'Sysnative', 'reg.exe'); // for 32-bit node on 64-bit OS
|
|
232
|
+
const set = new Set([sysnative, sys32, syswow64]);
|
|
233
|
+
return [...set].filter(p => p && fs.existsSync(p));
|
|
234
|
+
}
|
|
235
|
+
function execReg(args) {
|
|
236
|
+
for (const exe of regExeCandidates()) {
|
|
237
|
+
const out = spawnSync(exe, args, {
|
|
238
|
+
encoding: 'utf8',
|
|
239
|
+
windowsHide: true,
|
|
240
|
+
shell: false,
|
|
241
|
+
});
|
|
242
|
+
if (!out.error && out.stdout)
|
|
243
|
+
return out.stdout;
|
|
244
|
+
}
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
function readRegVals(hiveKey) {
|
|
248
|
+
const stdout = execReg(['query', hiveKey]);
|
|
249
|
+
if (!stdout)
|
|
250
|
+
return null;
|
|
251
|
+
const take = (name) => {
|
|
252
|
+
const m = stdout.match(new RegExp(`\\s${name}\\s+REG_\\w+\\s+(.+)$`, 'mi'));
|
|
253
|
+
return m ? m[1].trim() : '';
|
|
254
|
+
};
|
|
255
|
+
return {
|
|
256
|
+
ProxyEnable: take('ProxyEnable'),
|
|
257
|
+
ProxyServer: take('ProxyServer'),
|
|
258
|
+
ProxyOverride: take('ProxyOverride'),
|
|
259
|
+
AutoConfigURL: take('AutoConfigURL'),
|
|
260
|
+
AutoDetect: take('AutoDetect'),
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
function parseWindowsRegistry() {
|
|
264
|
+
const HKCU = 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings';
|
|
265
|
+
const HKLM = 'HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings';
|
|
266
|
+
const cu = readRegVals(HKCU);
|
|
267
|
+
const lm = readRegVals(HKLM);
|
|
268
|
+
const v = cu || lm;
|
|
269
|
+
if (!v)
|
|
270
|
+
return null;
|
|
271
|
+
const info = {};
|
|
272
|
+
const enabledManual = !!v.ProxyEnable && v.ProxyEnable !== '0' && v.ProxyEnable.toLowerCase() !== '0x0';
|
|
273
|
+
const enabledAuto = !!v.AutoDetect && v.AutoDetect !== '0' && v.AutoDetect.toLowerCase() !== '0x0';
|
|
274
|
+
const anyEnabled = enabledManual || enabledAuto || !!v.AutoConfigURL;
|
|
275
|
+
// PAC + autodetect (only when something is actually enabled)
|
|
276
|
+
if (v.AutoConfigURL)
|
|
277
|
+
info.pacUrl = v.AutoConfigURL.trim(); // PAC stands on its own
|
|
278
|
+
if (enabledAuto)
|
|
279
|
+
info.autoDetect = true; // autodetect still gated
|
|
280
|
+
// Manual proxies
|
|
281
|
+
if (enabledManual && v.ProxyServer) {
|
|
282
|
+
const s = v.ProxyServer.trim();
|
|
283
|
+
if (s.includes('=')) {
|
|
284
|
+
for (const part of s.split(';')) {
|
|
285
|
+
const [proto, addr] = part.split('=');
|
|
286
|
+
if (!proto || !addr)
|
|
287
|
+
continue;
|
|
288
|
+
const p = proto.trim().toLowerCase();
|
|
289
|
+
const a = stripScheme(addr.trim());
|
|
290
|
+
if (p === 'http')
|
|
291
|
+
info.http = a;
|
|
292
|
+
else if (p === 'https')
|
|
293
|
+
info.https = a;
|
|
294
|
+
else if (p === 'socks' || p === 'socks5')
|
|
295
|
+
info.socks = a;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
const a = stripScheme(s);
|
|
300
|
+
info.http = a;
|
|
301
|
+
info.https = a;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
// Bypass list
|
|
305
|
+
if (anyEnabled && v.ProxyOverride)
|
|
306
|
+
info.bypassList = semiJoin(v.ProxyOverride.split(/[,;]/));
|
|
307
|
+
// Env creds as global fallback (Windows does not store proxy creds in ProxyServer)
|
|
308
|
+
const { username, password } = readCredsFromEnv();
|
|
309
|
+
if (username && password) {
|
|
310
|
+
info.username = username;
|
|
311
|
+
info.password = password;
|
|
312
|
+
}
|
|
313
|
+
return (info.pacUrl || info.http || info.https || info.socks || info.autoDetect || info.bypassList) ? info : null;
|
|
314
|
+
}
|
|
315
|
+
/* ============================ Public API ============================ */
|
|
316
|
+
export function getProxyInfo() {
|
|
317
|
+
const plat = os.platform();
|
|
318
|
+
if (plat === 'win32')
|
|
319
|
+
return parseEnvProxyCommon() || parseWindowsRegistry();
|
|
320
|
+
if (plat === 'darwin')
|
|
321
|
+
return parseEnvProxyCommon() || parseMacScutil();
|
|
322
|
+
return parseEnvProxyCommon(); // Linux/others
|
|
323
|
+
}
|
|
324
|
+
export function proxyInfoToResolution(info) {
|
|
325
|
+
if (!info)
|
|
326
|
+
return { kind: 'none' };
|
|
327
|
+
// Prefer manual proxies first (these work with Playwright's proxy option)
|
|
328
|
+
if (info.http) {
|
|
329
|
+
return { kind: 'manual', settings: {
|
|
330
|
+
server: `http://${info.http}`,
|
|
331
|
+
username: info.username,
|
|
332
|
+
password: info.password,
|
|
333
|
+
bypass: info.bypassList,
|
|
334
|
+
} };
|
|
335
|
+
}
|
|
336
|
+
if (info.https) {
|
|
337
|
+
return { kind: 'manual', settings: {
|
|
338
|
+
server: `http://${info.https}`,
|
|
339
|
+
username: info.username,
|
|
340
|
+
password: info.password,
|
|
341
|
+
bypass: info.bypassList,
|
|
342
|
+
} };
|
|
343
|
+
}
|
|
344
|
+
if (info.socks) {
|
|
345
|
+
return { kind: 'manual', settings: {
|
|
346
|
+
server: `socks5://${info.socks}`,
|
|
347
|
+
username: info.username,
|
|
348
|
+
password: info.password,
|
|
349
|
+
bypass: info.bypassList,
|
|
350
|
+
} };
|
|
351
|
+
}
|
|
352
|
+
// PAC → handle via Chromium args; do NOT try proxy.server = 'pac+...'
|
|
353
|
+
if (info.pacUrl) {
|
|
354
|
+
// Minor hardening: prefer 127.0.0.1 over localhost for loopback PAC
|
|
355
|
+
const pacUrl = info.pacUrl.replace('://localhost', '://127.0.0.1');
|
|
356
|
+
return { kind: 'pac', pacUrl, bypass: info.bypassList };
|
|
357
|
+
}
|
|
358
|
+
// Auto-detect not supported directly
|
|
359
|
+
return { kind: 'none' };
|
|
360
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { generateHtmlReport } from './generateHtmlReport.js';
|
|
3
|
+
async function main() {
|
|
4
|
+
const dirArg = process.argv[2];
|
|
5
|
+
if (!dirArg) {
|
|
6
|
+
console.error('Usage: npx tsx src/dev-generate-from-existing.ts <results-dir>');
|
|
7
|
+
process.exit(1);
|
|
8
|
+
}
|
|
9
|
+
const resultDir = path.resolve(process.cwd(), dirArg);
|
|
10
|
+
const out = await generateHtmlReport(resultDir);
|
|
11
|
+
console.log('\nOpen:', out);
|
|
12
|
+
}
|
|
13
|
+
main().catch((e) => {
|
|
14
|
+
console.error(e);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
});
|