@xcelera/cli 2.2.1 → 2.3.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 +60 -0
- package/dist/action.js +83 -12
- package/dist/action.js.map +1 -1
- package/dist/cli.js +116 -17
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
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(url, token, context) {
|
|
65
|
+
async function requestAudit(url, token, context, auth) {
|
|
66
66
|
const apiUrl = `${getApiBaseUrl()}/api/v1/audit`;
|
|
67
67
|
try {
|
|
68
68
|
const response = await fetch(apiUrl, {
|
|
@@ -73,7 +73,8 @@ async function requestAudit(url, token, context) {
|
|
|
73
73
|
},
|
|
74
74
|
body: JSON.stringify({
|
|
75
75
|
url,
|
|
76
|
-
context
|
|
76
|
+
context,
|
|
77
|
+
...(auth && { auth })
|
|
77
78
|
})
|
|
78
79
|
});
|
|
79
80
|
if (!response.ok) {
|
|
@@ -105,6 +106,7 @@ async function requestAudit(url, token, context) {
|
|
|
105
106
|
throw error;
|
|
106
107
|
}
|
|
107
108
|
}
|
|
109
|
+
/* istanbul ignore next */
|
|
108
110
|
function getApiBaseUrl() {
|
|
109
111
|
if (process.env.NODE_ENV === 'development') {
|
|
110
112
|
return 'http://localhost:3000';
|
|
@@ -9303,24 +9305,21 @@ async function isGitRepository() {
|
|
|
9303
9305
|
return simpleGit().checkIsRepo();
|
|
9304
9306
|
}
|
|
9305
9307
|
async function getCommit(hash = 'HEAD') {
|
|
9306
|
-
// format: %H: commit hash, %s: subject, %an: author name, %
|
|
9308
|
+
// format: %H: commit hash, %s: subject, %an: author name, %ai: author date
|
|
9307
9309
|
// Use null byte (%x00) as delimiter - cannot appear in commit data
|
|
9308
9310
|
const commit = await simpleGit().show([
|
|
9309
9311
|
hash,
|
|
9310
9312
|
'--no-patch',
|
|
9311
|
-
'--format=%H%x00%s%x00%an%x00%
|
|
9313
|
+
'--format=%H%x00%s%x00%an%x00%ai'
|
|
9312
9314
|
]);
|
|
9313
|
-
const [resolvedHash, message, author_name,
|
|
9314
|
-
.trim()
|
|
9315
|
-
.split('\0');
|
|
9315
|
+
const [resolvedHash, message, author_name, date] = commit.trim().split('\0');
|
|
9316
9316
|
if (!resolvedHash) {
|
|
9317
9317
|
throw new Error(`No commit found for ${hash}`);
|
|
9318
9318
|
}
|
|
9319
9319
|
return {
|
|
9320
9320
|
hash: resolvedHash,
|
|
9321
9321
|
message: message,
|
|
9322
|
-
author: author_name || 'Unknown',
|
|
9323
|
-
email: author_email || '',
|
|
9322
|
+
author: author_name || 'Unknown Author',
|
|
9324
9323
|
date: date ? new Date(date).toISOString() : new Date().toISOString()
|
|
9325
9324
|
};
|
|
9326
9325
|
}
|
|
@@ -9341,13 +9340,18 @@ async function inferBuildContext() {
|
|
|
9341
9340
|
};
|
|
9342
9341
|
}
|
|
9343
9342
|
|
|
9344
|
-
async function runAuditCommand(url, token) {
|
|
9343
|
+
async function runAuditCommand(url, token, authOptions) {
|
|
9345
9344
|
const output = [];
|
|
9346
9345
|
const errors = [];
|
|
9347
9346
|
try {
|
|
9348
9347
|
const buildContext = await inferBuildContext();
|
|
9349
9348
|
output.push(...formatBuildContext(buildContext));
|
|
9350
|
-
const
|
|
9349
|
+
const auth = parseAuthCredentials(authOptions);
|
|
9350
|
+
if (auth) {
|
|
9351
|
+
output.push('🔐 Authentication credentials detected');
|
|
9352
|
+
output.push('');
|
|
9353
|
+
}
|
|
9354
|
+
const response = await requestAudit(url, token, buildContext, auth);
|
|
9351
9355
|
if (!response.success) {
|
|
9352
9356
|
const { message, details } = response.error;
|
|
9353
9357
|
errors.push('❌ Unable to schedule audit :(');
|
|
@@ -9439,6 +9443,59 @@ function formatGitHubIntegrationStatus(context) {
|
|
|
9439
9443
|
}
|
|
9440
9444
|
return { output, errors };
|
|
9441
9445
|
}
|
|
9446
|
+
function parseAuthCredentials(options) {
|
|
9447
|
+
const envAuth = process.env.XCELERA_AUTH;
|
|
9448
|
+
if (envAuth) {
|
|
9449
|
+
try {
|
|
9450
|
+
return JSON.parse(envAuth);
|
|
9451
|
+
}
|
|
9452
|
+
catch {
|
|
9453
|
+
throw new Error('XCELERA_AUTH environment variable contains invalid JSON');
|
|
9454
|
+
}
|
|
9455
|
+
}
|
|
9456
|
+
// Check for --auth JSON option
|
|
9457
|
+
if (options?.authJson) {
|
|
9458
|
+
try {
|
|
9459
|
+
return JSON.parse(options.authJson);
|
|
9460
|
+
}
|
|
9461
|
+
catch {
|
|
9462
|
+
throw new Error('--auth option contains invalid JSON');
|
|
9463
|
+
}
|
|
9464
|
+
}
|
|
9465
|
+
// Build auth from --cookie and --header options
|
|
9466
|
+
const hasCookies = options?.cookies && options.cookies.length > 0;
|
|
9467
|
+
const hasHeaders = options?.headers && options.headers.length > 0;
|
|
9468
|
+
if (!hasCookies && !hasHeaders) {
|
|
9469
|
+
return undefined;
|
|
9470
|
+
}
|
|
9471
|
+
const auth = {};
|
|
9472
|
+
if (hasCookies && options.cookies) {
|
|
9473
|
+
auth.cookies = options.cookies.map(parseCookie);
|
|
9474
|
+
}
|
|
9475
|
+
if (hasHeaders && options.headers) {
|
|
9476
|
+
auth.headers = {};
|
|
9477
|
+
for (const header of options.headers) {
|
|
9478
|
+
const colonIndex = header.indexOf(':');
|
|
9479
|
+
if (colonIndex === -1) {
|
|
9480
|
+
throw new Error(`Invalid header format: "${header}". Expected "Name: Value"`);
|
|
9481
|
+
}
|
|
9482
|
+
const name = header.slice(0, colonIndex).trim();
|
|
9483
|
+
const value = header.slice(colonIndex + 1).trim();
|
|
9484
|
+
auth.headers[name] = value;
|
|
9485
|
+
}
|
|
9486
|
+
}
|
|
9487
|
+
return auth;
|
|
9488
|
+
}
|
|
9489
|
+
function parseCookie(cookie) {
|
|
9490
|
+
const equalsIndex = cookie.indexOf('=');
|
|
9491
|
+
if (equalsIndex === -1) {
|
|
9492
|
+
throw new Error(`Invalid cookie format: "${cookie}". Expected "name=value"`);
|
|
9493
|
+
}
|
|
9494
|
+
return {
|
|
9495
|
+
name: cookie.slice(0, equalsIndex),
|
|
9496
|
+
value: cookie.slice(equalsIndex + 1)
|
|
9497
|
+
};
|
|
9498
|
+
}
|
|
9442
9499
|
|
|
9443
9500
|
/* istanbul ignore file */
|
|
9444
9501
|
const options = {
|
|
@@ -9450,6 +9507,17 @@ const options = {
|
|
|
9450
9507
|
type: 'string',
|
|
9451
9508
|
required: true,
|
|
9452
9509
|
default: process.env.XCELERA_TOKEN
|
|
9510
|
+
},
|
|
9511
|
+
auth: {
|
|
9512
|
+
type: 'string'
|
|
9513
|
+
},
|
|
9514
|
+
cookie: {
|
|
9515
|
+
type: 'string',
|
|
9516
|
+
multiple: true
|
|
9517
|
+
},
|
|
9518
|
+
header: {
|
|
9519
|
+
type: 'string',
|
|
9520
|
+
multiple: true
|
|
9453
9521
|
}
|
|
9454
9522
|
};
|
|
9455
9523
|
const { positionals, values } = parseArgs({
|
|
@@ -9472,7 +9540,7 @@ if (command !== 'audit') {
|
|
|
9472
9540
|
printHelp();
|
|
9473
9541
|
process.exit(1);
|
|
9474
9542
|
}
|
|
9475
|
-
const { url, token } = values;
|
|
9543
|
+
const { url, token, auth, cookie, header } = values;
|
|
9476
9544
|
if (!url) {
|
|
9477
9545
|
console.error('URL is required. Use --url <url> to specify the URL to audit.');
|
|
9478
9546
|
process.exit(1);
|
|
@@ -9481,17 +9549,48 @@ if (!token) {
|
|
|
9481
9549
|
console.error('A token is required. Use --token or set XCELERA_TOKEN environment variable.');
|
|
9482
9550
|
process.exit(1);
|
|
9483
9551
|
}
|
|
9484
|
-
const result = await runAuditCommand(url, token
|
|
9552
|
+
const result = await runAuditCommand(url, token, {
|
|
9553
|
+
authJson: auth,
|
|
9554
|
+
cookies: cookie,
|
|
9555
|
+
headers: header
|
|
9556
|
+
});
|
|
9485
9557
|
result.output.forEach((line) => console.log(line));
|
|
9486
9558
|
result.errors.forEach((line) => console.error(line));
|
|
9487
9559
|
process.exit(result.exitCode);
|
|
9488
9560
|
function printHelp() {
|
|
9489
|
-
console.log('Usage: xcelera audit --url <url> [
|
|
9561
|
+
console.log('Usage: xcelera audit --url <url> [options]');
|
|
9490
9562
|
console.log('');
|
|
9491
9563
|
console.log('Options:');
|
|
9492
|
-
console.log(' --token <token>
|
|
9493
|
-
console.log('Can also be set with
|
|
9494
|
-
console.log(' --url <url>
|
|
9564
|
+
console.log(' --token <token> The xcelera API token.');
|
|
9565
|
+
console.log(' Can also be set with XCELERA_TOKEN env var.');
|
|
9566
|
+
console.log(' --url <url> The URL to audit.');
|
|
9567
|
+
console.log('');
|
|
9568
|
+
console.log('Authentication (for pages behind login):');
|
|
9569
|
+
console.log(' --cookie <cookie> Cookie in "name=value" format.');
|
|
9570
|
+
console.log(' Can be specified multiple times.');
|
|
9571
|
+
console.log(' --header <header> Header in "Name: Value" format.');
|
|
9572
|
+
console.log(' Can be specified multiple times.');
|
|
9573
|
+
console.log(' --auth <json> Full auth config as JSON.');
|
|
9574
|
+
console.log(' Can also be set with XCELERA_AUTH env var.');
|
|
9575
|
+
console.log('');
|
|
9576
|
+
console.log('Examples:');
|
|
9577
|
+
console.log(' # Basic audit');
|
|
9578
|
+
console.log(' xcelera audit --url https://example.com');
|
|
9579
|
+
console.log('');
|
|
9580
|
+
console.log(' # With session cookie');
|
|
9581
|
+
console.log(' xcelera audit --url https://myapp.com/dashboard --cookie "session=abc123"');
|
|
9582
|
+
console.log('');
|
|
9583
|
+
console.log(' # With bearer token');
|
|
9584
|
+
console.log(' xcelera audit --url https://api.myapp.com/admin \\');
|
|
9585
|
+
console.log(' --header "Authorization: Bearer eyJhbG..."');
|
|
9586
|
+
console.log('');
|
|
9587
|
+
console.log(' # Multiple cookies');
|
|
9588
|
+
console.log(' xcelera audit --url https://myapp.com/dashboard \\');
|
|
9589
|
+
console.log(' --cookie "session=abc123" --cookie "csrf=xyz"');
|
|
9590
|
+
console.log('');
|
|
9591
|
+
console.log(' # Full auth JSON (multiple cookies and headers)');
|
|
9592
|
+
console.log(' xcelera audit --url https://myapp.com/dashboard \\');
|
|
9593
|
+
console.log(' --auth \'{"cookies":[{"name":"session","value":"abc123"}],"headers":{"X-Custom":"value"}}\'');
|
|
9495
9594
|
console.log('');
|
|
9496
9595
|
}
|
|
9497
9596
|
//# sourceMappingURL=cli.js.map
|