@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 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
- # Full auth JSON (multiple cookies and custom headers)
30
+ # With Netscape cookie file (cookies.txt)
31
31
  xcelera audit --url https://myapp.com/dashboard \
32
- --auth '{"cookies":[{"name":"session","value":"abc123"}],"headers":{"X-Custom":"value"}}'
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
- throw new Error('No git repository detected.');
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
- ...(await inferGitContext()),
36566
- branch: ('prBranch' in ciEnv && ciEnv.prBranch ? ciEnv.prBranch : ciEnv.branch) ??
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 envAuth = process.env.XCELERA_AUTH;
36683
- if (envAuth) {
36684
- try {
36685
- return JSON.parse(envAuth);
36686
- }
36687
- catch {
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
- // Check for --auth JSON option
36692
- if (options?.authJson) {
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 && options.cookies) {
36708
- auth.cookies = options.cookies.map(parseCookie);
36771
+ if (hasCookies) {
36772
+ auth.cookies = cookies;
36709
36773
  }
36710
- if (hasHeaders && options.headers) {
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 auth = coreExports.getInput('auth');
36817
+ const cookieFile = coreExports.getInput('cookie-file');
36754
36818
  const cookie = coreExports.getInput('cookie');
36755
36819
  const header = coreExports.getInput('header');
36756
- if (!auth && !cookie && !header) {
36820
+ if (!cookieFile && !cookie && !header) {
36757
36821
  return undefined;
36758
36822
  }
36759
36823
  return {
36760
- authJson: auth || undefined,
36824
+ cookieFile: cookieFile || undefined,
36761
36825
  cookies: cookie ? [cookie] : undefined,
36762
36826
  headers: header ? [header] : undefined
36763
36827
  };