@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/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';
|
|
@@ -9274,7 +9276,7 @@ var simpleGit = gitInstanceFactory;
|
|
|
9274
9276
|
|
|
9275
9277
|
async function inferGitContext() {
|
|
9276
9278
|
if (!(await isGitRepository())) {
|
|
9277
|
-
|
|
9279
|
+
return undefined;
|
|
9278
9280
|
}
|
|
9279
9281
|
const remoteUrl = await getRemoteUrl();
|
|
9280
9282
|
const parsed = parseGithubUrl(remoteUrl);
|
|
@@ -9324,27 +9326,30 @@ async function getCommit(hash = 'HEAD') {
|
|
|
9324
9326
|
|
|
9325
9327
|
async function inferBuildContext() {
|
|
9326
9328
|
const ciEnv = envCi();
|
|
9327
|
-
const gitContext =
|
|
9328
|
-
|
|
9329
|
-
|
|
9330
|
-
undefined
|
|
9331
|
-
};
|
|
9329
|
+
const gitContext = await inferGitContext();
|
|
9330
|
+
const branch = ('prBranch' in ciEnv && ciEnv.prBranch ? ciEnv.prBranch : ciEnv.branch) ??
|
|
9331
|
+
undefined;
|
|
9332
9332
|
return {
|
|
9333
9333
|
service: ciEnv.isCi ? ciEnv.service : 'unknown',
|
|
9334
9334
|
prNumber: 'pr' in ciEnv ? ciEnv.pr : undefined,
|
|
9335
9335
|
buildNumber: 'build' in ciEnv ? ciEnv.build : undefined,
|
|
9336
9336
|
buildUrl: 'buildUrl' in ciEnv ? ciEnv.buildUrl : undefined,
|
|
9337
|
-
git: gitContext
|
|
9337
|
+
git: gitContext ? { ...gitContext, branch } : undefined
|
|
9338
9338
|
};
|
|
9339
9339
|
}
|
|
9340
9340
|
|
|
9341
|
-
async function runAuditCommand(url, token) {
|
|
9341
|
+
async function runAuditCommand(url, token, authOptions) {
|
|
9342
9342
|
const output = [];
|
|
9343
9343
|
const errors = [];
|
|
9344
9344
|
try {
|
|
9345
9345
|
const buildContext = await inferBuildContext();
|
|
9346
9346
|
output.push(...formatBuildContext(buildContext));
|
|
9347
|
-
const
|
|
9347
|
+
const auth = parseAuthCredentials(authOptions);
|
|
9348
|
+
if (auth) {
|
|
9349
|
+
output.push('🔐 Authentication credentials detected');
|
|
9350
|
+
output.push('');
|
|
9351
|
+
}
|
|
9352
|
+
const response = await requestAudit(url, token, buildContext, auth);
|
|
9348
9353
|
if (!response.success) {
|
|
9349
9354
|
const { message, details } = response.error;
|
|
9350
9355
|
errors.push('❌ Unable to schedule audit :(');
|
|
@@ -9436,6 +9441,59 @@ function formatGitHubIntegrationStatus(context) {
|
|
|
9436
9441
|
}
|
|
9437
9442
|
return { output, errors };
|
|
9438
9443
|
}
|
|
9444
|
+
function parseAuthCredentials(options) {
|
|
9445
|
+
const envAuth = process.env.XCELERA_AUTH;
|
|
9446
|
+
if (envAuth) {
|
|
9447
|
+
try {
|
|
9448
|
+
return JSON.parse(envAuth);
|
|
9449
|
+
}
|
|
9450
|
+
catch {
|
|
9451
|
+
throw new Error('XCELERA_AUTH environment variable contains invalid JSON');
|
|
9452
|
+
}
|
|
9453
|
+
}
|
|
9454
|
+
// Check for --auth JSON option
|
|
9455
|
+
if (options?.authJson) {
|
|
9456
|
+
try {
|
|
9457
|
+
return JSON.parse(options.authJson);
|
|
9458
|
+
}
|
|
9459
|
+
catch {
|
|
9460
|
+
throw new Error('--auth option contains invalid JSON');
|
|
9461
|
+
}
|
|
9462
|
+
}
|
|
9463
|
+
// Build auth from --cookie and --header options
|
|
9464
|
+
const hasCookies = options?.cookies && options.cookies.length > 0;
|
|
9465
|
+
const hasHeaders = options?.headers && options.headers.length > 0;
|
|
9466
|
+
if (!hasCookies && !hasHeaders) {
|
|
9467
|
+
return undefined;
|
|
9468
|
+
}
|
|
9469
|
+
const auth = {};
|
|
9470
|
+
if (hasCookies && options.cookies) {
|
|
9471
|
+
auth.cookies = options.cookies.map(parseCookie);
|
|
9472
|
+
}
|
|
9473
|
+
if (hasHeaders && options.headers) {
|
|
9474
|
+
auth.headers = {};
|
|
9475
|
+
for (const header of options.headers) {
|
|
9476
|
+
const colonIndex = header.indexOf(':');
|
|
9477
|
+
if (colonIndex === -1) {
|
|
9478
|
+
throw new Error(`Invalid header format: "${header}". Expected "Name: Value"`);
|
|
9479
|
+
}
|
|
9480
|
+
const name = header.slice(0, colonIndex).trim();
|
|
9481
|
+
const value = header.slice(colonIndex + 1).trim();
|
|
9482
|
+
auth.headers[name] = value;
|
|
9483
|
+
}
|
|
9484
|
+
}
|
|
9485
|
+
return auth;
|
|
9486
|
+
}
|
|
9487
|
+
function parseCookie(cookie) {
|
|
9488
|
+
const equalsIndex = cookie.indexOf('=');
|
|
9489
|
+
if (equalsIndex === -1) {
|
|
9490
|
+
throw new Error(`Invalid cookie format: "${cookie}". Expected "name=value"`);
|
|
9491
|
+
}
|
|
9492
|
+
return {
|
|
9493
|
+
name: cookie.slice(0, equalsIndex),
|
|
9494
|
+
value: cookie.slice(equalsIndex + 1)
|
|
9495
|
+
};
|
|
9496
|
+
}
|
|
9439
9497
|
|
|
9440
9498
|
/* istanbul ignore file */
|
|
9441
9499
|
const options = {
|
|
@@ -9447,6 +9505,17 @@ const options = {
|
|
|
9447
9505
|
type: 'string',
|
|
9448
9506
|
required: true,
|
|
9449
9507
|
default: process.env.XCELERA_TOKEN
|
|
9508
|
+
},
|
|
9509
|
+
auth: {
|
|
9510
|
+
type: 'string'
|
|
9511
|
+
},
|
|
9512
|
+
cookie: {
|
|
9513
|
+
type: 'string',
|
|
9514
|
+
multiple: true
|
|
9515
|
+
},
|
|
9516
|
+
header: {
|
|
9517
|
+
type: 'string',
|
|
9518
|
+
multiple: true
|
|
9450
9519
|
}
|
|
9451
9520
|
};
|
|
9452
9521
|
const { positionals, values } = parseArgs({
|
|
@@ -9469,7 +9538,7 @@ if (command !== 'audit') {
|
|
|
9469
9538
|
printHelp();
|
|
9470
9539
|
process.exit(1);
|
|
9471
9540
|
}
|
|
9472
|
-
const { url, token } = values;
|
|
9541
|
+
const { url, token, auth, cookie, header } = values;
|
|
9473
9542
|
if (!url) {
|
|
9474
9543
|
console.error('URL is required. Use --url <url> to specify the URL to audit.');
|
|
9475
9544
|
process.exit(1);
|
|
@@ -9478,17 +9547,48 @@ if (!token) {
|
|
|
9478
9547
|
console.error('A token is required. Use --token or set XCELERA_TOKEN environment variable.');
|
|
9479
9548
|
process.exit(1);
|
|
9480
9549
|
}
|
|
9481
|
-
const result = await runAuditCommand(url, token
|
|
9550
|
+
const result = await runAuditCommand(url, token, {
|
|
9551
|
+
authJson: auth,
|
|
9552
|
+
cookies: cookie,
|
|
9553
|
+
headers: header
|
|
9554
|
+
});
|
|
9482
9555
|
result.output.forEach((line) => console.log(line));
|
|
9483
9556
|
result.errors.forEach((line) => console.error(line));
|
|
9484
9557
|
process.exit(result.exitCode);
|
|
9485
9558
|
function printHelp() {
|
|
9486
|
-
console.log('Usage: xcelera audit --url <url> [
|
|
9559
|
+
console.log('Usage: xcelera audit --url <url> [options]');
|
|
9487
9560
|
console.log('');
|
|
9488
9561
|
console.log('Options:');
|
|
9489
|
-
console.log(' --token <token>
|
|
9490
|
-
console.log('Can also be set with
|
|
9491
|
-
console.log(' --url <url>
|
|
9562
|
+
console.log(' --token <token> The xcelera API token.');
|
|
9563
|
+
console.log(' Can also be set with XCELERA_TOKEN env var.');
|
|
9564
|
+
console.log(' --url <url> The URL to audit.');
|
|
9565
|
+
console.log('');
|
|
9566
|
+
console.log('Authentication (for pages behind login):');
|
|
9567
|
+
console.log(' --cookie <cookie> Cookie in "name=value" format.');
|
|
9568
|
+
console.log(' Can be specified multiple times.');
|
|
9569
|
+
console.log(' --header <header> Header in "Name: Value" format.');
|
|
9570
|
+
console.log(' Can be specified multiple times.');
|
|
9571
|
+
console.log(' --auth <json> Full auth config as JSON.');
|
|
9572
|
+
console.log(' Can also be set with XCELERA_AUTH env var.');
|
|
9573
|
+
console.log('');
|
|
9574
|
+
console.log('Examples:');
|
|
9575
|
+
console.log(' # Basic audit');
|
|
9576
|
+
console.log(' xcelera audit --url https://example.com');
|
|
9577
|
+
console.log('');
|
|
9578
|
+
console.log(' # With session cookie');
|
|
9579
|
+
console.log(' xcelera audit --url https://myapp.com/dashboard --cookie "session=abc123"');
|
|
9580
|
+
console.log('');
|
|
9581
|
+
console.log(' # With bearer token');
|
|
9582
|
+
console.log(' xcelera audit --url https://api.myapp.com/admin \\');
|
|
9583
|
+
console.log(' --header "Authorization: Bearer eyJhbG..."');
|
|
9584
|
+
console.log('');
|
|
9585
|
+
console.log(' # Multiple cookies');
|
|
9586
|
+
console.log(' xcelera audit --url https://myapp.com/dashboard \\');
|
|
9587
|
+
console.log(' --cookie "session=abc123" --cookie "csrf=xyz"');
|
|
9588
|
+
console.log('');
|
|
9589
|
+
console.log(' # Full auth JSON (multiple cookies and headers)');
|
|
9590
|
+
console.log(' xcelera audit --url https://myapp.com/dashboard \\');
|
|
9591
|
+
console.log(' --auth \'{"cookies":[{"name":"session","value":"abc123"}],"headers":{"X-Custom":"value"}}\'');
|
|
9492
9592
|
console.log('');
|
|
9493
9593
|
}
|
|
9494
9594
|
//# sourceMappingURL=cli.js.map
|