@xcelera/cli 2.3.1 → 2.4.0
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/README.md +10 -5
- package/dist/action.js +93 -27
- package/dist/action.js.map +1 -1
- package/dist/cli.js +98 -32
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,13 +27,10 @@ xcelera audit --url https://api.myapp.com/admin \
|
|
|
27
27
|
xcelera audit --url https://myapp.com/dashboard \
|
|
28
28
|
--cookie "session=abc123" --cookie "csrf=xyz"
|
|
29
29
|
|
|
30
|
-
#
|
|
30
|
+
# With Netscape cookie file (cookies.txt)
|
|
31
31
|
xcelera audit --url https://myapp.com/dashboard \
|
|
32
|
-
--
|
|
32
|
+
--cookie-file ./cookies.txt
|
|
33
33
|
|
|
34
|
-
# Using environment variable
|
|
35
|
-
export XCELERA_AUTH='{"cookies":[{"name":"session","value":"abc123"}]}'
|
|
36
|
-
xcelera audit --url https://myapp.com/dashboard
|
|
37
34
|
```
|
|
38
35
|
|
|
39
36
|
### GitHub Action Usage
|
|
@@ -57,6 +54,14 @@ For authenticated pages in CI:
|
|
|
57
54
|
token: ${{ secrets.XCELERA_TOKEN }}
|
|
58
55
|
cookie: "session=value"
|
|
59
56
|
|
|
57
|
+
# With Netscape cookie file (cookies.txt)
|
|
58
|
+
- name: Lighthouse Audit (Cookie File Auth)
|
|
59
|
+
uses: xcelera/cli@v1
|
|
60
|
+
with:
|
|
61
|
+
url: https://example.com/dashboard
|
|
62
|
+
token: ${{ secrets.XCELERA_TOKEN }}
|
|
63
|
+
cookie-file: ./cookies.txt
|
|
64
|
+
|
|
60
65
|
# With bearer token header
|
|
61
66
|
- name: Lighthouse Audit (Bearer Auth)
|
|
62
67
|
uses: xcelera/cli@v1
|
package/dist/action.js
CHANGED
|
@@ -36573,13 +36573,88 @@ async function inferBuildContext() {
|
|
|
36573
36573
|
};
|
|
36574
36574
|
}
|
|
36575
36575
|
|
|
36576
|
+
function readNetscapeCookieFileSync(filePath, now = new Date()) {
|
|
36577
|
+
try {
|
|
36578
|
+
const contents = readFileSync(filePath, 'utf8');
|
|
36579
|
+
return parseNetscapeCookieFileContents(contents, filePath, now);
|
|
36580
|
+
}
|
|
36581
|
+
catch (error) {
|
|
36582
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
36583
|
+
throw new Error(`Unable to read cookie file "${filePath}": ${message}`);
|
|
36584
|
+
}
|
|
36585
|
+
}
|
|
36586
|
+
function parseNetscapeCookieFileContents(contents, sourceLabel = 'cookie file', now = new Date()) {
|
|
36587
|
+
const cookies = [];
|
|
36588
|
+
const expiredCookieNames = [];
|
|
36589
|
+
const nowEpochSeconds = Math.floor(now.getTime() / 1000);
|
|
36590
|
+
const lines = contents.split(/\r?\n/);
|
|
36591
|
+
for (let index = 0; index < lines.length; index++) {
|
|
36592
|
+
const rawLine = lines[index];
|
|
36593
|
+
const line = rawLine.trim();
|
|
36594
|
+
if (!line) {
|
|
36595
|
+
continue;
|
|
36596
|
+
}
|
|
36597
|
+
if (line.startsWith('#') && !line.startsWith('#HttpOnly_')) {
|
|
36598
|
+
continue;
|
|
36599
|
+
}
|
|
36600
|
+
const fields = line.split('\t');
|
|
36601
|
+
if (fields.length !== 7) {
|
|
36602
|
+
throw new Error(`Invalid Netscape cookie line ${index + 1} in ${sourceLabel}: expected 7 tab-separated fields`);
|
|
36603
|
+
}
|
|
36604
|
+
let domain = fields[0];
|
|
36605
|
+
const path = fields[2] || undefined;
|
|
36606
|
+
const secure = toBool(fields[3]);
|
|
36607
|
+
const expiresEpochSeconds = parseEpochSeconds(fields[4], index + 1, sourceLabel);
|
|
36608
|
+
const name = fields[5];
|
|
36609
|
+
const value = fields[6] ?? '';
|
|
36610
|
+
let httpOnly;
|
|
36611
|
+
if (domain.startsWith('#HttpOnly_')) {
|
|
36612
|
+
httpOnly = true;
|
|
36613
|
+
domain = domain.slice('#HttpOnly_'.length);
|
|
36614
|
+
}
|
|
36615
|
+
const isExpired = expiresEpochSeconds > 0 && expiresEpochSeconds < nowEpochSeconds;
|
|
36616
|
+
if (isExpired) {
|
|
36617
|
+
expiredCookieNames.push(name);
|
|
36618
|
+
continue;
|
|
36619
|
+
}
|
|
36620
|
+
const cookie = {
|
|
36621
|
+
name,
|
|
36622
|
+
value,
|
|
36623
|
+
...(domain && { domain }),
|
|
36624
|
+
...(path && { path }),
|
|
36625
|
+
...(secure && { secure: true }),
|
|
36626
|
+
...(httpOnly && { httpOnly: true })
|
|
36627
|
+
};
|
|
36628
|
+
cookies.push(cookie);
|
|
36629
|
+
}
|
|
36630
|
+
const warnings = [];
|
|
36631
|
+
if (expiredCookieNames.length > 0) {
|
|
36632
|
+
const preview = expiredCookieNames.slice(0, 5).join(', ');
|
|
36633
|
+
const suffix = expiredCookieNames.length > 5 ? ', …' : '';
|
|
36634
|
+
warnings.push(`⚠️ Dropped ${expiredCookieNames.length} expired cookie(s) from ${sourceLabel}: ${preview}${suffix}`);
|
|
36635
|
+
}
|
|
36636
|
+
return { cookies, warnings };
|
|
36637
|
+
}
|
|
36638
|
+
function toBool(value) {
|
|
36639
|
+
return value.trim().toUpperCase() === 'TRUE';
|
|
36640
|
+
}
|
|
36641
|
+
function parseEpochSeconds(value, lineNumber, sourceLabel) {
|
|
36642
|
+
const trimmed = value.trim();
|
|
36643
|
+
const num = Number.parseInt(trimmed, 10);
|
|
36644
|
+
if (Number.isNaN(num)) {
|
|
36645
|
+
throw new Error(`Invalid expiration epoch seconds at line ${lineNumber} in ${sourceLabel}`);
|
|
36646
|
+
}
|
|
36647
|
+
return num;
|
|
36648
|
+
}
|
|
36649
|
+
|
|
36576
36650
|
async function runAuditCommand(url, token, authOptions) {
|
|
36577
36651
|
const output = [];
|
|
36578
36652
|
const errors = [];
|
|
36579
36653
|
try {
|
|
36580
36654
|
const buildContext = await inferBuildContext();
|
|
36581
36655
|
output.push(...formatBuildContext(buildContext));
|
|
36582
|
-
const auth = parseAuthCredentials(authOptions);
|
|
36656
|
+
const { auth, warnings } = parseAuthCredentials(authOptions);
|
|
36657
|
+
errors.push(...warnings);
|
|
36583
36658
|
if (auth) {
|
|
36584
36659
|
output.push('🔐 Authentication credentials detected');
|
|
36585
36660
|
output.push('');
|
|
@@ -36677,35 +36752,26 @@ function formatGitHubIntegrationStatus(context) {
|
|
|
36677
36752
|
return { output, errors };
|
|
36678
36753
|
}
|
|
36679
36754
|
function parseAuthCredentials(options) {
|
|
36680
|
-
const
|
|
36681
|
-
|
|
36682
|
-
|
|
36683
|
-
|
|
36684
|
-
|
|
36685
|
-
|
|
36686
|
-
throw new Error('XCELERA_AUTH environment variable contains invalid JSON');
|
|
36687
|
-
}
|
|
36755
|
+
const warnings = [];
|
|
36756
|
+
const cookies = [];
|
|
36757
|
+
if (options?.cookieFile) {
|
|
36758
|
+
const parsed = readNetscapeCookieFileSync(options.cookieFile);
|
|
36759
|
+
warnings.push(...parsed.warnings);
|
|
36760
|
+
cookies.push(...parsed.cookies);
|
|
36688
36761
|
}
|
|
36689
|
-
|
|
36690
|
-
|
|
36691
|
-
try {
|
|
36692
|
-
return JSON.parse(options.authJson);
|
|
36693
|
-
}
|
|
36694
|
-
catch {
|
|
36695
|
-
throw new Error('--auth option contains invalid JSON');
|
|
36696
|
-
}
|
|
36762
|
+
if (options?.cookies && options.cookies.length > 0) {
|
|
36763
|
+
cookies.push(...options.cookies.map(parseCookie));
|
|
36697
36764
|
}
|
|
36698
|
-
// Build auth from --cookie and --header options
|
|
36699
|
-
const hasCookies = options?.cookies && options.cookies.length > 0;
|
|
36700
36765
|
const hasHeaders = options?.headers && options.headers.length > 0;
|
|
36766
|
+
const hasCookies = cookies.length > 0;
|
|
36701
36767
|
if (!hasCookies && !hasHeaders) {
|
|
36702
|
-
return undefined;
|
|
36768
|
+
return { auth: undefined, warnings };
|
|
36703
36769
|
}
|
|
36704
36770
|
const auth = {};
|
|
36705
|
-
if (hasCookies
|
|
36706
|
-
auth.cookies =
|
|
36771
|
+
if (hasCookies) {
|
|
36772
|
+
auth.cookies = cookies;
|
|
36707
36773
|
}
|
|
36708
|
-
if (hasHeaders && options
|
|
36774
|
+
if (hasHeaders && options?.headers) {
|
|
36709
36775
|
auth.headers = {};
|
|
36710
36776
|
for (const header of options.headers) {
|
|
36711
36777
|
const colonIndex = header.indexOf(':');
|
|
@@ -36717,7 +36783,7 @@ function parseAuthCredentials(options) {
|
|
|
36717
36783
|
auth.headers[name] = value;
|
|
36718
36784
|
}
|
|
36719
36785
|
}
|
|
36720
|
-
return auth;
|
|
36786
|
+
return { auth, warnings };
|
|
36721
36787
|
}
|
|
36722
36788
|
function parseCookie(cookie) {
|
|
36723
36789
|
const equalsIndex = cookie.indexOf('=');
|
|
@@ -36748,14 +36814,14 @@ async function run() {
|
|
|
36748
36814
|
}
|
|
36749
36815
|
}
|
|
36750
36816
|
function parseAuthInputs() {
|
|
36751
|
-
const
|
|
36817
|
+
const cookieFile = coreExports.getInput('cookie-file');
|
|
36752
36818
|
const cookie = coreExports.getInput('cookie');
|
|
36753
36819
|
const header = coreExports.getInput('header');
|
|
36754
|
-
if (!
|
|
36820
|
+
if (!cookieFile && !cookie && !header) {
|
|
36755
36821
|
return undefined;
|
|
36756
36822
|
}
|
|
36757
36823
|
return {
|
|
36758
|
-
|
|
36824
|
+
cookieFile: cookieFile || undefined,
|
|
36759
36825
|
cookies: cookie ? [cookie] : undefined,
|
|
36760
36826
|
headers: header ? [header] : undefined
|
|
36761
36827
|
};
|