@xcelera/cli 2.3.0 → 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 +98 -34
- package/dist/action.js.map +1 -1
- package/dist/cli.js +103 -39
- package/dist/cli.js.map +1 -1
- package/package.json +4 -2
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
|
@@ -36511,7 +36511,7 @@ var simpleGit = gitInstanceFactory;
|
|
|
36511
36511
|
|
|
36512
36512
|
async function inferGitContext() {
|
|
36513
36513
|
if (!(await isGitRepository())) {
|
|
36514
|
-
|
|
36514
|
+
return undefined;
|
|
36515
36515
|
}
|
|
36516
36516
|
const remoteUrl = await getRemoteUrl();
|
|
36517
36517
|
const parsed = parseGithubUrl(remoteUrl);
|
|
@@ -36561,27 +36561,100 @@ async function getCommit(hash = 'HEAD') {
|
|
|
36561
36561
|
|
|
36562
36562
|
async function inferBuildContext() {
|
|
36563
36563
|
const ciEnv = envCi();
|
|
36564
|
-
const gitContext =
|
|
36565
|
-
|
|
36566
|
-
|
|
36567
|
-
undefined
|
|
36568
|
-
};
|
|
36564
|
+
const gitContext = await inferGitContext();
|
|
36565
|
+
const branch = ('prBranch' in ciEnv && ciEnv.prBranch ? ciEnv.prBranch : ciEnv.branch) ??
|
|
36566
|
+
undefined;
|
|
36569
36567
|
return {
|
|
36570
36568
|
service: ciEnv.isCi ? ciEnv.service : 'unknown',
|
|
36571
36569
|
prNumber: 'pr' in ciEnv ? ciEnv.pr : undefined,
|
|
36572
36570
|
buildNumber: 'build' in ciEnv ? ciEnv.build : undefined,
|
|
36573
36571
|
buildUrl: 'buildUrl' in ciEnv ? ciEnv.buildUrl : undefined,
|
|
36574
|
-
git: gitContext
|
|
36572
|
+
git: gitContext ? { ...gitContext, branch } : undefined
|
|
36575
36573
|
};
|
|
36576
36574
|
}
|
|
36577
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
|
+
|
|
36578
36650
|
async function runAuditCommand(url, token, authOptions) {
|
|
36579
36651
|
const output = [];
|
|
36580
36652
|
const errors = [];
|
|
36581
36653
|
try {
|
|
36582
36654
|
const buildContext = await inferBuildContext();
|
|
36583
36655
|
output.push(...formatBuildContext(buildContext));
|
|
36584
|
-
const auth = parseAuthCredentials(authOptions);
|
|
36656
|
+
const { auth, warnings } = parseAuthCredentials(authOptions);
|
|
36657
|
+
errors.push(...warnings);
|
|
36585
36658
|
if (auth) {
|
|
36586
36659
|
output.push('🔐 Authentication credentials detected');
|
|
36587
36660
|
output.push('');
|
|
@@ -36679,35 +36752,26 @@ function formatGitHubIntegrationStatus(context) {
|
|
|
36679
36752
|
return { output, errors };
|
|
36680
36753
|
}
|
|
36681
36754
|
function parseAuthCredentials(options) {
|
|
36682
|
-
const
|
|
36683
|
-
|
|
36684
|
-
|
|
36685
|
-
|
|
36686
|
-
|
|
36687
|
-
|
|
36688
|
-
throw new Error('XCELERA_AUTH environment variable contains invalid JSON');
|
|
36689
|
-
}
|
|
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);
|
|
36690
36761
|
}
|
|
36691
|
-
|
|
36692
|
-
|
|
36693
|
-
try {
|
|
36694
|
-
return JSON.parse(options.authJson);
|
|
36695
|
-
}
|
|
36696
|
-
catch {
|
|
36697
|
-
throw new Error('--auth option contains invalid JSON');
|
|
36698
|
-
}
|
|
36762
|
+
if (options?.cookies && options.cookies.length > 0) {
|
|
36763
|
+
cookies.push(...options.cookies.map(parseCookie));
|
|
36699
36764
|
}
|
|
36700
|
-
// Build auth from --cookie and --header options
|
|
36701
|
-
const hasCookies = options?.cookies && options.cookies.length > 0;
|
|
36702
36765
|
const hasHeaders = options?.headers && options.headers.length > 0;
|
|
36766
|
+
const hasCookies = cookies.length > 0;
|
|
36703
36767
|
if (!hasCookies && !hasHeaders) {
|
|
36704
|
-
return undefined;
|
|
36768
|
+
return { auth: undefined, warnings };
|
|
36705
36769
|
}
|
|
36706
36770
|
const auth = {};
|
|
36707
|
-
if (hasCookies
|
|
36708
|
-
auth.cookies =
|
|
36771
|
+
if (hasCookies) {
|
|
36772
|
+
auth.cookies = cookies;
|
|
36709
36773
|
}
|
|
36710
|
-
if (hasHeaders && options
|
|
36774
|
+
if (hasHeaders && options?.headers) {
|
|
36711
36775
|
auth.headers = {};
|
|
36712
36776
|
for (const header of options.headers) {
|
|
36713
36777
|
const colonIndex = header.indexOf(':');
|
|
@@ -36719,7 +36783,7 @@ function parseAuthCredentials(options) {
|
|
|
36719
36783
|
auth.headers[name] = value;
|
|
36720
36784
|
}
|
|
36721
36785
|
}
|
|
36722
|
-
return auth;
|
|
36786
|
+
return { auth, warnings };
|
|
36723
36787
|
}
|
|
36724
36788
|
function parseCookie(cookie) {
|
|
36725
36789
|
const equalsIndex = cookie.indexOf('=');
|
|
@@ -36750,14 +36814,14 @@ async function run() {
|
|
|
36750
36814
|
}
|
|
36751
36815
|
}
|
|
36752
36816
|
function parseAuthInputs() {
|
|
36753
|
-
const
|
|
36817
|
+
const cookieFile = coreExports.getInput('cookie-file');
|
|
36754
36818
|
const cookie = coreExports.getInput('cookie');
|
|
36755
36819
|
const header = coreExports.getInput('header');
|
|
36756
|
-
if (!
|
|
36820
|
+
if (!cookieFile && !cookie && !header) {
|
|
36757
36821
|
return undefined;
|
|
36758
36822
|
}
|
|
36759
36823
|
return {
|
|
36760
|
-
|
|
36824
|
+
cookieFile: cookieFile || undefined,
|
|
36761
36825
|
cookies: cookie ? [cookie] : undefined,
|
|
36762
36826
|
headers: header ? [header] : undefined
|
|
36763
36827
|
};
|