@xcelera/cli 2.3.1 → 3.0.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/NOTICE +744 -0
- package/README.md +19 -14
- package/dist/action.js +534 -216
- package/dist/action.js.map +1 -1
- package/dist/cli.js +113 -47
- package/dist/cli.js.map +1 -1
- package/package.json +8 -15
package/dist/cli.js
CHANGED
|
@@ -62,7 +62,7 @@ function isNetworkError(error) {
|
|
|
62
62
|
return errorMessages.has(message);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
async function requestAudit(
|
|
65
|
+
async function requestAudit(ref, token, context, auth) {
|
|
66
66
|
const apiUrl = `${getApiBaseUrl()}/api/v1/audit`;
|
|
67
67
|
try {
|
|
68
68
|
const response = await fetch(apiUrl, {
|
|
@@ -72,7 +72,7 @@ async function requestAudit(url, token, context, auth) {
|
|
|
72
72
|
Authorization: `Bearer ${token}`
|
|
73
73
|
},
|
|
74
74
|
body: JSON.stringify({
|
|
75
|
-
|
|
75
|
+
ref,
|
|
76
76
|
context,
|
|
77
77
|
...(auth && { auth })
|
|
78
78
|
})
|
|
@@ -9338,24 +9338,99 @@ async function inferBuildContext() {
|
|
|
9338
9338
|
};
|
|
9339
9339
|
}
|
|
9340
9340
|
|
|
9341
|
-
|
|
9341
|
+
function readNetscapeCookieFileSync(filePath, now = new Date()) {
|
|
9342
|
+
try {
|
|
9343
|
+
const contents = readFileSync(filePath, 'utf8');
|
|
9344
|
+
return parseNetscapeCookieFileContents(contents, filePath, now);
|
|
9345
|
+
}
|
|
9346
|
+
catch (error) {
|
|
9347
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
9348
|
+
throw new Error(`Unable to read cookie file "${filePath}": ${message}`);
|
|
9349
|
+
}
|
|
9350
|
+
}
|
|
9351
|
+
function parseNetscapeCookieFileContents(contents, sourceLabel = 'cookie file', now = new Date()) {
|
|
9352
|
+
const cookies = [];
|
|
9353
|
+
const expiredCookieNames = [];
|
|
9354
|
+
const nowEpochSeconds = Math.floor(now.getTime() / 1000);
|
|
9355
|
+
const lines = contents.split(/\r?\n/);
|
|
9356
|
+
for (let index = 0; index < lines.length; index++) {
|
|
9357
|
+
const rawLine = lines[index];
|
|
9358
|
+
const line = rawLine.trim();
|
|
9359
|
+
if (!line) {
|
|
9360
|
+
continue;
|
|
9361
|
+
}
|
|
9362
|
+
if (line.startsWith('#') && !line.startsWith('#HttpOnly_')) {
|
|
9363
|
+
continue;
|
|
9364
|
+
}
|
|
9365
|
+
const fields = line.split('\t');
|
|
9366
|
+
if (fields.length !== 7) {
|
|
9367
|
+
throw new Error(`Invalid Netscape cookie line ${index + 1} in ${sourceLabel}: expected 7 tab-separated fields`);
|
|
9368
|
+
}
|
|
9369
|
+
let domain = fields[0];
|
|
9370
|
+
const path = fields[2] || undefined;
|
|
9371
|
+
const secure = toBool(fields[3]);
|
|
9372
|
+
const expiresEpochSeconds = parseEpochSeconds(fields[4], index + 1, sourceLabel);
|
|
9373
|
+
const name = fields[5];
|
|
9374
|
+
const value = fields[6] ?? '';
|
|
9375
|
+
let httpOnly;
|
|
9376
|
+
if (domain.startsWith('#HttpOnly_')) {
|
|
9377
|
+
httpOnly = true;
|
|
9378
|
+
domain = domain.slice('#HttpOnly_'.length);
|
|
9379
|
+
}
|
|
9380
|
+
const isExpired = expiresEpochSeconds > 0 && expiresEpochSeconds < nowEpochSeconds;
|
|
9381
|
+
if (isExpired) {
|
|
9382
|
+
expiredCookieNames.push(name);
|
|
9383
|
+
continue;
|
|
9384
|
+
}
|
|
9385
|
+
const cookie = {
|
|
9386
|
+
name,
|
|
9387
|
+
value,
|
|
9388
|
+
...(domain && { domain }),
|
|
9389
|
+
...(path && { path }),
|
|
9390
|
+
...(secure && { secure: true }),
|
|
9391
|
+
...(httpOnly && { httpOnly: true })
|
|
9392
|
+
};
|
|
9393
|
+
cookies.push(cookie);
|
|
9394
|
+
}
|
|
9395
|
+
const warnings = [];
|
|
9396
|
+
if (expiredCookieNames.length > 0) {
|
|
9397
|
+
const preview = expiredCookieNames.slice(0, 5).join(', ');
|
|
9398
|
+
const suffix = expiredCookieNames.length > 5 ? ', …' : '';
|
|
9399
|
+
warnings.push(`⚠️ Dropped ${expiredCookieNames.length} expired cookie(s) from ${sourceLabel}: ${preview}${suffix}`);
|
|
9400
|
+
}
|
|
9401
|
+
return { cookies, warnings };
|
|
9402
|
+
}
|
|
9403
|
+
function toBool(value) {
|
|
9404
|
+
return value.trim().toUpperCase() === 'TRUE';
|
|
9405
|
+
}
|
|
9406
|
+
function parseEpochSeconds(value, lineNumber, sourceLabel) {
|
|
9407
|
+
const trimmed = value.trim();
|
|
9408
|
+
const num = Number.parseInt(trimmed, 10);
|
|
9409
|
+
if (Number.isNaN(num)) {
|
|
9410
|
+
throw new Error(`Invalid expiration epoch seconds at line ${lineNumber} in ${sourceLabel}`);
|
|
9411
|
+
}
|
|
9412
|
+
return num;
|
|
9413
|
+
}
|
|
9414
|
+
|
|
9415
|
+
async function runAuditCommand(ref, token, authOptions) {
|
|
9342
9416
|
const output = [];
|
|
9343
9417
|
const errors = [];
|
|
9344
9418
|
try {
|
|
9345
9419
|
const buildContext = await inferBuildContext();
|
|
9346
9420
|
output.push(...formatBuildContext(buildContext));
|
|
9347
|
-
const auth = parseAuthCredentials(authOptions);
|
|
9421
|
+
const { auth, warnings } = parseAuthCredentials(authOptions);
|
|
9422
|
+
errors.push(...warnings);
|
|
9348
9423
|
if (auth) {
|
|
9349
9424
|
output.push('🔐 Authentication credentials detected');
|
|
9350
9425
|
output.push('');
|
|
9351
9426
|
}
|
|
9352
|
-
const response = await requestAudit(
|
|
9427
|
+
const response = await requestAudit(ref, token, buildContext, auth);
|
|
9353
9428
|
if (!response.success) {
|
|
9354
9429
|
const { message, details } = response.error;
|
|
9355
9430
|
errors.push('❌ Unable to schedule audit :(');
|
|
9356
9431
|
errors.push(` ↳ ${message}`);
|
|
9357
9432
|
if (details) {
|
|
9358
|
-
errors.push(` ↳ ${details}`);
|
|
9433
|
+
errors.push(` ↳ ${JSON.stringify(details)}`);
|
|
9359
9434
|
}
|
|
9360
9435
|
return { exitCode: 1, output, errors };
|
|
9361
9436
|
}
|
|
@@ -9442,35 +9517,26 @@ function formatGitHubIntegrationStatus(context) {
|
|
|
9442
9517
|
return { output, errors };
|
|
9443
9518
|
}
|
|
9444
9519
|
function parseAuthCredentials(options) {
|
|
9445
|
-
const
|
|
9446
|
-
|
|
9447
|
-
|
|
9448
|
-
|
|
9449
|
-
|
|
9450
|
-
|
|
9451
|
-
throw new Error('XCELERA_AUTH environment variable contains invalid JSON');
|
|
9452
|
-
}
|
|
9520
|
+
const warnings = [];
|
|
9521
|
+
const cookies = [];
|
|
9522
|
+
if (options?.cookieFile) {
|
|
9523
|
+
const parsed = readNetscapeCookieFileSync(options.cookieFile);
|
|
9524
|
+
warnings.push(...parsed.warnings);
|
|
9525
|
+
cookies.push(...parsed.cookies);
|
|
9453
9526
|
}
|
|
9454
|
-
|
|
9455
|
-
|
|
9456
|
-
try {
|
|
9457
|
-
return JSON.parse(options.authJson);
|
|
9458
|
-
}
|
|
9459
|
-
catch {
|
|
9460
|
-
throw new Error('--auth option contains invalid JSON');
|
|
9461
|
-
}
|
|
9527
|
+
if (options?.cookies && options.cookies.length > 0) {
|
|
9528
|
+
cookies.push(...options.cookies.map(parseCookie));
|
|
9462
9529
|
}
|
|
9463
|
-
// Build auth from --cookie and --header options
|
|
9464
|
-
const hasCookies = options?.cookies && options.cookies.length > 0;
|
|
9465
9530
|
const hasHeaders = options?.headers && options.headers.length > 0;
|
|
9531
|
+
const hasCookies = cookies.length > 0;
|
|
9466
9532
|
if (!hasCookies && !hasHeaders) {
|
|
9467
|
-
return undefined;
|
|
9533
|
+
return { auth: undefined, warnings };
|
|
9468
9534
|
}
|
|
9469
9535
|
const auth = {};
|
|
9470
|
-
if (hasCookies
|
|
9471
|
-
auth.cookies =
|
|
9536
|
+
if (hasCookies) {
|
|
9537
|
+
auth.cookies = cookies;
|
|
9472
9538
|
}
|
|
9473
|
-
if (hasHeaders && options
|
|
9539
|
+
if (hasHeaders && options?.headers) {
|
|
9474
9540
|
auth.headers = {};
|
|
9475
9541
|
for (const header of options.headers) {
|
|
9476
9542
|
const colonIndex = header.indexOf(':');
|
|
@@ -9482,7 +9548,7 @@ function parseAuthCredentials(options) {
|
|
|
9482
9548
|
auth.headers[name] = value;
|
|
9483
9549
|
}
|
|
9484
9550
|
}
|
|
9485
|
-
return auth;
|
|
9551
|
+
return { auth, warnings };
|
|
9486
9552
|
}
|
|
9487
9553
|
function parseCookie(cookie) {
|
|
9488
9554
|
const equalsIndex = cookie.indexOf('=');
|
|
@@ -9497,7 +9563,7 @@ function parseCookie(cookie) {
|
|
|
9497
9563
|
|
|
9498
9564
|
/* istanbul ignore file */
|
|
9499
9565
|
const options = {
|
|
9500
|
-
|
|
9566
|
+
ref: {
|
|
9501
9567
|
type: 'string',
|
|
9502
9568
|
required: true
|
|
9503
9569
|
},
|
|
@@ -9506,7 +9572,7 @@ const options = {
|
|
|
9506
9572
|
required: true,
|
|
9507
9573
|
default: process.env.XCELERA_TOKEN
|
|
9508
9574
|
},
|
|
9509
|
-
|
|
9575
|
+
'cookie-file': {
|
|
9510
9576
|
type: 'string'
|
|
9511
9577
|
},
|
|
9512
9578
|
cookie: {
|
|
@@ -9538,17 +9604,18 @@ if (command !== 'audit') {
|
|
|
9538
9604
|
printHelp();
|
|
9539
9605
|
process.exit(1);
|
|
9540
9606
|
}
|
|
9541
|
-
const {
|
|
9542
|
-
|
|
9543
|
-
|
|
9607
|
+
const { ref, token, cookie, header } = values;
|
|
9608
|
+
const cookieFile = values['cookie-file'];
|
|
9609
|
+
if (!ref) {
|
|
9610
|
+
console.error('Page ref is required. Use --ref <ref> to specify the page reference to audit. This can be found on the page definition in the xcelera dashboard.');
|
|
9544
9611
|
process.exit(1);
|
|
9545
9612
|
}
|
|
9546
9613
|
if (!token) {
|
|
9547
9614
|
console.error('A token is required. Use --token or set XCELERA_TOKEN environment variable.');
|
|
9548
9615
|
process.exit(1);
|
|
9549
9616
|
}
|
|
9550
|
-
const result = await runAuditCommand(
|
|
9551
|
-
|
|
9617
|
+
const result = await runAuditCommand(ref, token, {
|
|
9618
|
+
cookieFile,
|
|
9552
9619
|
cookies: cookie,
|
|
9553
9620
|
headers: header
|
|
9554
9621
|
});
|
|
@@ -9556,39 +9623,38 @@ result.output.forEach((line) => console.log(line));
|
|
|
9556
9623
|
result.errors.forEach((line) => console.error(line));
|
|
9557
9624
|
process.exit(result.exitCode);
|
|
9558
9625
|
function printHelp() {
|
|
9559
|
-
console.log('Usage: xcelera audit --
|
|
9626
|
+
console.log('Usage: xcelera audit --ref <ref> [options]');
|
|
9560
9627
|
console.log('');
|
|
9561
9628
|
console.log('Options:');
|
|
9562
9629
|
console.log(' --token <token> The xcelera API token.');
|
|
9563
9630
|
console.log(' Can also be set with XCELERA_TOKEN env var.');
|
|
9564
|
-
console.log(' --
|
|
9631
|
+
console.log(' --ref <ref> The reference of the page to audit.');
|
|
9565
9632
|
console.log('');
|
|
9566
9633
|
console.log('Authentication (for pages behind login):');
|
|
9567
9634
|
console.log(' --cookie <cookie> Cookie in "name=value" format.');
|
|
9568
9635
|
console.log(' Can be specified multiple times.');
|
|
9569
9636
|
console.log(' --header <header> Header in "Name: Value" format.');
|
|
9570
9637
|
console.log(' Can be specified multiple times.');
|
|
9571
|
-
console.log(' --
|
|
9572
|
-
console.log('
|
|
9638
|
+
console.log(' --cookie-file <path> Netscape cookie file (cookies.txt).');
|
|
9639
|
+
console.log(' Expired cookies are ignored with a warning.');
|
|
9573
9640
|
console.log('');
|
|
9574
9641
|
console.log('Examples:');
|
|
9575
9642
|
console.log(' # Basic audit');
|
|
9576
|
-
console.log(' xcelera audit --
|
|
9643
|
+
console.log(' xcelera audit --ref example-page-xdfd');
|
|
9577
9644
|
console.log('');
|
|
9578
9645
|
console.log(' # With session cookie');
|
|
9579
|
-
console.log(' xcelera audit --
|
|
9646
|
+
console.log(' xcelera audit --ref example-page-xdfd --cookie "session=abc123"');
|
|
9580
9647
|
console.log('');
|
|
9581
9648
|
console.log(' # With bearer token');
|
|
9582
|
-
console.log(' xcelera audit --
|
|
9649
|
+
console.log(' xcelera audit --ref example-page-xdfd \\');
|
|
9583
9650
|
console.log(' --header "Authorization: Bearer eyJhbG..."');
|
|
9584
9651
|
console.log('');
|
|
9585
9652
|
console.log(' # Multiple cookies');
|
|
9586
|
-
console.log(' xcelera audit --
|
|
9653
|
+
console.log(' xcelera audit --ref example-page-xdfd \\');
|
|
9587
9654
|
console.log(' --cookie "session=abc123" --cookie "csrf=xyz"');
|
|
9588
9655
|
console.log('');
|
|
9589
|
-
console.log(' #
|
|
9590
|
-
console.log(' xcelera audit --
|
|
9591
|
-
console.log(' --auth \'{"cookies":[{"name":"session","value":"abc123"}],"headers":{"X-Custom":"value"}}\'');
|
|
9656
|
+
console.log(' # With cookie file (Netscape cookies.txt format)');
|
|
9657
|
+
console.log(' xcelera audit --ref example-page-xdfd --cookie-file ./cookies.txt');
|
|
9592
9658
|
console.log('');
|
|
9593
9659
|
}
|
|
9594
9660
|
//# sourceMappingURL=cli.js.map
|