@xcelera/cli 2.2.2 → 2.3.1
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 +60 -0
- package/dist/action.js +84 -12
- package/dist/action.js.map +1 -1
- package/dist/cli.js +117 -17
- package/dist/cli.js.map +1 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -4,6 +4,38 @@ A CLI for running Lighthouse performance audits using xcelera.dev
|
|
|
4
4
|
|
|
5
5
|
## Usage
|
|
6
6
|
|
|
7
|
+
### CLI Usage
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# Basic audit
|
|
11
|
+
xcelera audit --url https://example.com --token your-api-token
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
### Authenticated Pages
|
|
15
|
+
|
|
16
|
+
For pages behind login, you can pass authentication credentials:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# With session cookie
|
|
20
|
+
xcelera audit --url https://myapp.com/dashboard --cookie "session=abc123"
|
|
21
|
+
|
|
22
|
+
# With bearer token header
|
|
23
|
+
xcelera audit --url https://api.myapp.com/admin \
|
|
24
|
+
--header "Authorization: Bearer eyJhbG..."
|
|
25
|
+
|
|
26
|
+
# Multiple cookies
|
|
27
|
+
xcelera audit --url https://myapp.com/dashboard \
|
|
28
|
+
--cookie "session=abc123" --cookie "csrf=xyz"
|
|
29
|
+
|
|
30
|
+
# Full auth JSON (multiple cookies and custom headers)
|
|
31
|
+
xcelera audit --url https://myapp.com/dashboard \
|
|
32
|
+
--auth '{"cookies":[{"name":"session","value":"abc123"}],"headers":{"X-Custom":"value"}}'
|
|
33
|
+
|
|
34
|
+
# Using environment variable
|
|
35
|
+
export XCELERA_AUTH='{"cookies":[{"name":"session","value":"abc123"}]}'
|
|
36
|
+
xcelera audit --url https://myapp.com/dashboard
|
|
37
|
+
```
|
|
38
|
+
|
|
7
39
|
### GitHub Action Usage
|
|
8
40
|
|
|
9
41
|
```yaml
|
|
@@ -14,6 +46,34 @@ A CLI for running Lighthouse performance audits using xcelera.dev
|
|
|
14
46
|
token: ${{ secrets.XCELERA_TOKEN }}
|
|
15
47
|
```
|
|
16
48
|
|
|
49
|
+
For authenticated pages in CI:
|
|
50
|
+
|
|
51
|
+
```yaml
|
|
52
|
+
# With session cookie
|
|
53
|
+
- name: Lighthouse Audit (Cookie Auth)
|
|
54
|
+
uses: xcelera/cli@v1
|
|
55
|
+
with:
|
|
56
|
+
url: https://example.com/dashboard
|
|
57
|
+
token: ${{ secrets.XCELERA_TOKEN }}
|
|
58
|
+
cookie: "session=value"
|
|
59
|
+
|
|
60
|
+
# With bearer token header
|
|
61
|
+
- name: Lighthouse Audit (Bearer Auth)
|
|
62
|
+
uses: xcelera/cli@v1
|
|
63
|
+
with:
|
|
64
|
+
url: https://example.com/admin
|
|
65
|
+
token: ${{ secrets.XCELERA_TOKEN }}
|
|
66
|
+
header: "Authorization: Bearer eybDfd..."
|
|
67
|
+
|
|
68
|
+
# With full auth JSON (multiple cookies/headers)
|
|
69
|
+
- name: Lighthouse Audit (Full Auth)
|
|
70
|
+
uses: xcelera/cli@v1
|
|
71
|
+
with:
|
|
72
|
+
url: https://example.com/dashboard
|
|
73
|
+
token: ${{ secrets.XCELERA_TOKEN }}
|
|
74
|
+
auth: '{"cookies":[{"name":"session","value":"session_value"},{"name":"csrf","value":"csrf_value"}]}'
|
|
75
|
+
```
|
|
76
|
+
|
|
17
77
|
## Setup
|
|
18
78
|
|
|
19
79
|
### 1. Get Your API Token
|
package/dist/action.js
CHANGED
|
@@ -27303,7 +27303,7 @@ function isNetworkError(error) {
|
|
|
27303
27303
|
return errorMessages.has(message);
|
|
27304
27304
|
}
|
|
27305
27305
|
|
|
27306
|
-
async function requestAudit(url, token, context) {
|
|
27306
|
+
async function requestAudit(url, token, context, auth) {
|
|
27307
27307
|
const apiUrl = `${getApiBaseUrl()}/api/v1/audit`;
|
|
27308
27308
|
try {
|
|
27309
27309
|
const response = await fetch(apiUrl, {
|
|
@@ -27314,7 +27314,8 @@ async function requestAudit(url, token, context) {
|
|
|
27314
27314
|
},
|
|
27315
27315
|
body: JSON.stringify({
|
|
27316
27316
|
url,
|
|
27317
|
-
context
|
|
27317
|
+
context,
|
|
27318
|
+
...(auth && { auth })
|
|
27318
27319
|
})
|
|
27319
27320
|
});
|
|
27320
27321
|
if (!response.ok) {
|
|
@@ -27346,6 +27347,7 @@ async function requestAudit(url, token, context) {
|
|
|
27346
27347
|
throw error;
|
|
27347
27348
|
}
|
|
27348
27349
|
}
|
|
27350
|
+
/* istanbul ignore next */
|
|
27349
27351
|
function getApiBaseUrl() {
|
|
27350
27352
|
if (process.env.NODE_ENV === 'development') {
|
|
27351
27353
|
return 'http://localhost:3000';
|
|
@@ -36509,7 +36511,7 @@ var simpleGit = gitInstanceFactory;
|
|
|
36509
36511
|
|
|
36510
36512
|
async function inferGitContext() {
|
|
36511
36513
|
if (!(await isGitRepository())) {
|
|
36512
|
-
|
|
36514
|
+
return undefined;
|
|
36513
36515
|
}
|
|
36514
36516
|
const remoteUrl = await getRemoteUrl();
|
|
36515
36517
|
const parsed = parseGithubUrl(remoteUrl);
|
|
@@ -36559,27 +36561,30 @@ async function getCommit(hash = 'HEAD') {
|
|
|
36559
36561
|
|
|
36560
36562
|
async function inferBuildContext() {
|
|
36561
36563
|
const ciEnv = envCi();
|
|
36562
|
-
const gitContext =
|
|
36563
|
-
|
|
36564
|
-
|
|
36565
|
-
undefined
|
|
36566
|
-
};
|
|
36564
|
+
const gitContext = await inferGitContext();
|
|
36565
|
+
const branch = ('prBranch' in ciEnv && ciEnv.prBranch ? ciEnv.prBranch : ciEnv.branch) ??
|
|
36566
|
+
undefined;
|
|
36567
36567
|
return {
|
|
36568
36568
|
service: ciEnv.isCi ? ciEnv.service : 'unknown',
|
|
36569
36569
|
prNumber: 'pr' in ciEnv ? ciEnv.pr : undefined,
|
|
36570
36570
|
buildNumber: 'build' in ciEnv ? ciEnv.build : undefined,
|
|
36571
36571
|
buildUrl: 'buildUrl' in ciEnv ? ciEnv.buildUrl : undefined,
|
|
36572
|
-
git: gitContext
|
|
36572
|
+
git: gitContext ? { ...gitContext, branch } : undefined
|
|
36573
36573
|
};
|
|
36574
36574
|
}
|
|
36575
36575
|
|
|
36576
|
-
async function runAuditCommand(url, token) {
|
|
36576
|
+
async function runAuditCommand(url, token, authOptions) {
|
|
36577
36577
|
const output = [];
|
|
36578
36578
|
const errors = [];
|
|
36579
36579
|
try {
|
|
36580
36580
|
const buildContext = await inferBuildContext();
|
|
36581
36581
|
output.push(...formatBuildContext(buildContext));
|
|
36582
|
-
const
|
|
36582
|
+
const auth = parseAuthCredentials(authOptions);
|
|
36583
|
+
if (auth) {
|
|
36584
|
+
output.push('🔐 Authentication credentials detected');
|
|
36585
|
+
output.push('');
|
|
36586
|
+
}
|
|
36587
|
+
const response = await requestAudit(url, token, buildContext, auth);
|
|
36583
36588
|
if (!response.success) {
|
|
36584
36589
|
const { message, details } = response.error;
|
|
36585
36590
|
errors.push('❌ Unable to schedule audit :(');
|
|
@@ -36671,13 +36676,67 @@ function formatGitHubIntegrationStatus(context) {
|
|
|
36671
36676
|
}
|
|
36672
36677
|
return { output, errors };
|
|
36673
36678
|
}
|
|
36679
|
+
function parseAuthCredentials(options) {
|
|
36680
|
+
const envAuth = process.env.XCELERA_AUTH;
|
|
36681
|
+
if (envAuth) {
|
|
36682
|
+
try {
|
|
36683
|
+
return JSON.parse(envAuth);
|
|
36684
|
+
}
|
|
36685
|
+
catch {
|
|
36686
|
+
throw new Error('XCELERA_AUTH environment variable contains invalid JSON');
|
|
36687
|
+
}
|
|
36688
|
+
}
|
|
36689
|
+
// Check for --auth JSON option
|
|
36690
|
+
if (options?.authJson) {
|
|
36691
|
+
try {
|
|
36692
|
+
return JSON.parse(options.authJson);
|
|
36693
|
+
}
|
|
36694
|
+
catch {
|
|
36695
|
+
throw new Error('--auth option contains invalid JSON');
|
|
36696
|
+
}
|
|
36697
|
+
}
|
|
36698
|
+
// Build auth from --cookie and --header options
|
|
36699
|
+
const hasCookies = options?.cookies && options.cookies.length > 0;
|
|
36700
|
+
const hasHeaders = options?.headers && options.headers.length > 0;
|
|
36701
|
+
if (!hasCookies && !hasHeaders) {
|
|
36702
|
+
return undefined;
|
|
36703
|
+
}
|
|
36704
|
+
const auth = {};
|
|
36705
|
+
if (hasCookies && options.cookies) {
|
|
36706
|
+
auth.cookies = options.cookies.map(parseCookie);
|
|
36707
|
+
}
|
|
36708
|
+
if (hasHeaders && options.headers) {
|
|
36709
|
+
auth.headers = {};
|
|
36710
|
+
for (const header of options.headers) {
|
|
36711
|
+
const colonIndex = header.indexOf(':');
|
|
36712
|
+
if (colonIndex === -1) {
|
|
36713
|
+
throw new Error(`Invalid header format: "${header}". Expected "Name: Value"`);
|
|
36714
|
+
}
|
|
36715
|
+
const name = header.slice(0, colonIndex).trim();
|
|
36716
|
+
const value = header.slice(colonIndex + 1).trim();
|
|
36717
|
+
auth.headers[name] = value;
|
|
36718
|
+
}
|
|
36719
|
+
}
|
|
36720
|
+
return auth;
|
|
36721
|
+
}
|
|
36722
|
+
function parseCookie(cookie) {
|
|
36723
|
+
const equalsIndex = cookie.indexOf('=');
|
|
36724
|
+
if (equalsIndex === -1) {
|
|
36725
|
+
throw new Error(`Invalid cookie format: "${cookie}". Expected "name=value"`);
|
|
36726
|
+
}
|
|
36727
|
+
return {
|
|
36728
|
+
name: cookie.slice(0, equalsIndex),
|
|
36729
|
+
value: cookie.slice(equalsIndex + 1)
|
|
36730
|
+
};
|
|
36731
|
+
}
|
|
36674
36732
|
|
|
36675
36733
|
/* istanbul ignore file */
|
|
36676
36734
|
run();
|
|
36677
36735
|
async function run() {
|
|
36678
36736
|
const url = coreExports.getInput('url', { required: true });
|
|
36679
36737
|
const token = coreExports.getInput('token', { required: true });
|
|
36680
|
-
const
|
|
36738
|
+
const authOptions = parseAuthInputs();
|
|
36739
|
+
const result = await runAuditCommand(url, token, authOptions);
|
|
36681
36740
|
result.output.forEach((line) => coreExports.info(line));
|
|
36682
36741
|
result.errors.forEach((line) => coreExports.error(line));
|
|
36683
36742
|
if (result.exitCode !== 0) {
|
|
@@ -36688,4 +36747,17 @@ async function run() {
|
|
|
36688
36747
|
coreExports.setOutput('status', 'success');
|
|
36689
36748
|
}
|
|
36690
36749
|
}
|
|
36750
|
+
function parseAuthInputs() {
|
|
36751
|
+
const auth = coreExports.getInput('auth');
|
|
36752
|
+
const cookie = coreExports.getInput('cookie');
|
|
36753
|
+
const header = coreExports.getInput('header');
|
|
36754
|
+
if (!auth && !cookie && !header) {
|
|
36755
|
+
return undefined;
|
|
36756
|
+
}
|
|
36757
|
+
return {
|
|
36758
|
+
authJson: auth || undefined,
|
|
36759
|
+
cookies: cookie ? [cookie] : undefined,
|
|
36760
|
+
headers: header ? [header] : undefined
|
|
36761
|
+
};
|
|
36762
|
+
}
|
|
36691
36763
|
//# sourceMappingURL=action.js.map
|