@aikidosec/ci-api-client 1.0.17 → 1.0.18

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
@@ -49,6 +49,7 @@ Options:
49
49
  "HIGH", "CRITICAL")
50
50
  --poll-interval [interval] The poll interval when checking for an updated scan result (preset: 10)
51
51
  --sla-mode Let Aikido only fail only on open issues that have gone out of SLA.
52
+ --json Output the scan result as JSON (gate_passed and issue_breakdown)
52
53
  -h, --help display help for command
53
54
  ```
54
55
 
@@ -91,7 +92,7 @@ Options:
91
92
 
92
93
  The CLI will spawn a cloud-based scan and then report scan progress. It will exit with exitCode `0` if the scan was successfull (`gate_passed: true`). The process will exit with exitCode `10` if the scan was unsuccesfull (`gate_passed: false`). If anything else goes wrong (e.g. API unavailable, scanning unavailable, other unexpected issue) the process will exit with exitCode `1`.
93
94
 
94
- If you want the scan to run quietly (without output), you can add the `--quiet` option to the command.
95
+ If you want the scan to run quietly (without output), you can add the `--quiet` option to the command. If you want machine-readable output instead, add `--json` to `scan-release`. That prints a JSON object with `gate_passed` and `issue_breakdown` and skips spinners and other extra info.
95
96
 
96
97
  Please note that the repository_id which you need to provide to the CLI is the unique ID of the Git provider you are using, not the ID of the repository in Aikido. You can also find this ID in Aikido, by going to the repository's detail page and clicking on the Git provider's icon in the header. Alternatively, you can pass the repository name as it is defined in Aikido.
97
98
 
@@ -30,8 +30,24 @@ export type TPollIsScanningResult = {
30
30
  secrets_scan_completed: boolean;
31
31
  sbom_scan_completed?: boolean;
32
32
  };
33
+ export type TIssueSeverityBreakdown = {
34
+ critical: number;
35
+ high: number;
36
+ medium: number;
37
+ low: number;
38
+ };
39
+ export type TIssueBreakdown = {
40
+ sast: TIssueSeverityBreakdown;
41
+ sca: TIssueSeverityBreakdown;
42
+ iac: TIssueSeverityBreakdown;
43
+ secrets: TIssueSeverityBreakdown;
44
+ malware: TIssueSeverityBreakdown;
45
+ license: TIssueSeverityBreakdown;
46
+ code_quality: TIssueSeverityBreakdown;
47
+ };
33
48
  type TPollScanCompletedOptions = {
34
49
  gate_passed: boolean;
50
+ issue_breakdown?: TIssueBreakdown;
35
51
  };
36
52
  type TPollScanFeatureBranchCompletedOptions = {
37
53
  new_issues_found: number;
@@ -118,7 +118,7 @@ export const scan = async ({ repoId, baseCommitId, headCommitId, branchName, opt
118
118
  pollStatus();
119
119
  };
120
120
  const parseCliOptions = (userCliOptions) => {
121
- const apiOptions = { version: '1.0.16' };
121
+ const apiOptions = { version: '1.0.18' };
122
122
  const cliOptions = { pollInterval: 5 };
123
123
  if (userCliOptions.pullRequestTitle) {
124
124
  apiOptions.pull_request_metadata = {
@@ -25,6 +25,7 @@ type TScanUserCliOptions = {
25
25
  pollInterval?: number;
26
26
  baseBranch?: string;
27
27
  slaMode?: boolean;
28
+ json?: boolean;
28
29
  };
29
30
  declare function cli(repoId: string, commitId: string, options: TScanUserCliOptions, command: string): Promise<void>;
30
31
  export declare const scan: ({ repoId, commitId, options, pollInterval, onStart, onStartComplete, onStartFail, onScanStart, onNextPoll, onScanComplete, onScanFail, }: TScanArguments) => Promise<void>;
@@ -2,7 +2,7 @@ import chalk from 'chalk';
2
2
  import { Argument, InvalidArgumentError, Option } from 'commander';
3
3
  import { pollScanStatus, startScan, } from '../aikidoApi.js';
4
4
  import { getApiKey } from '../configuration.js';
5
- import { outputError, outputHttpError, outputLog, startSpinner, } from '../output.js';
5
+ import { outputError, outputHttpError, outputJson, outputLog, startSpinner, } from '../output.js';
6
6
  async function cli(repoId, commitId, options, command) {
7
7
  const apiKey = getApiKey();
8
8
  if (!apiKey) {
@@ -11,6 +11,10 @@ async function cli(repoId, commitId, options, command) {
11
11
  const { apiOptions, cliOptions } = parseCliOptions(options);
12
12
  let loader;
13
13
  let pollCount = 1;
14
+ const jsonOutput = cliOptions.json;
15
+ if (jsonOutput) {
16
+ process.env.QUIET = true;
17
+ }
14
18
  const onStart = () => {
15
19
  loader = startSpinner('Starting Aikido Security release scan');
16
20
  };
@@ -27,6 +31,16 @@ async function cli(repoId, commitId, options, command) {
27
31
  loader = startSpinner('Waiting for scan to complete');
28
32
  };
29
33
  const onScanComplete = (pollResult) => {
34
+ if (jsonOutput) {
35
+ outputJson({
36
+ gate_passed: pollResult.gate_passed === true,
37
+ issue_breakdown: pollResult.issue_breakdown,
38
+ });
39
+ if (pollResult.gate_passed !== true) {
40
+ process.exitCode = 10;
41
+ }
42
+ return;
43
+ }
30
44
  if (pollResult.gate_passed === true) {
31
45
  loader?.succeed('Scan completed, no open issues found');
32
46
  if (pollResult.diff_url) {
@@ -123,8 +137,8 @@ export const scan = async ({ repoId, commitId, options, pollInterval = 10, onSta
123
137
  pollStatus();
124
138
  };
125
139
  const parseCliOptions = (userCliOptions) => {
126
- const apiOptions = { version: '1.0.16' };
127
- const cliOptions = { pollInterval: 10 };
140
+ const apiOptions = { version: '1.0.18' };
141
+ const cliOptions = { pollInterval: 10, json: false };
128
142
  if (userCliOptions.pullRequestTitle) {
129
143
  apiOptions.pull_request_metadata = {
130
144
  ...(apiOptions.pull_request_metadata ?? {}),
@@ -168,6 +182,9 @@ const parseCliOptions = (userCliOptions) => {
168
182
  else if (userCliOptions.pollInterval) {
169
183
  cliOptions.pollInterval = userCliOptions.pollInterval;
170
184
  }
185
+ if (userCliOptions.json) {
186
+ cliOptions.json = true;
187
+ }
171
188
  return { apiOptions, cliOptions };
172
189
  };
173
190
  const validateCommitId = (value) => {
@@ -193,6 +210,7 @@ export const cliSetup = (program) => program
193
210
  .argParser(parseFloat))
194
211
  .addOption(new Option('--base-branch <branchname>', 'Base branch for the release gated scan.'))
195
212
  .option('--sla-mode', 'Let Aikido only fail only on open issues that have gone out of SLA.')
213
+ .option('--json', 'Output the scan result as JSON (gate_passed and issue_breakdown)')
196
214
  .description('Run a release scan of an Aikido repo.')
197
215
  .action(cli);
198
216
  export default { cli, cliSetup, scan };
package/lib/index.js CHANGED
@@ -11,7 +11,7 @@ export const program = new Command();
11
11
  program
12
12
  .name('Aikido API Client')
13
13
  .description('CLI api client to easily integrate the Aikido public CI API into custom deploy scripts')
14
- .version('1.0.16');
14
+ .version('1.0.18');
15
15
  apiKey.cliSetup(program);
16
16
  scan.cliSetup(program);
17
17
  scanRelease.cliSetup(program);
package/lib/output.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Ora } from 'ora';
2
2
  import { AxiosError } from 'axios';
3
3
  export declare const outputLog: (message: string) => void;
4
+ export declare const outputJson: (data: unknown) => void;
4
5
  export declare const outputDebug: (variable: any) => void;
5
6
  export declare const outputError: (message: string, exitCode?: number) => void;
6
7
  export declare const outputHttpError: (axiosError: AxiosError) => void;
package/lib/output.js CHANGED
@@ -6,6 +6,9 @@ export const outputLog = (message) => {
6
6
  console.log(message);
7
7
  }
8
8
  };
9
+ export const outputJson = (data) => {
10
+ console.log(JSON.stringify(data, null, 2));
11
+ };
9
12
  export const outputDebug = (variable) => {
10
13
  if (process.env.CLI_DEBUG == true || process.env.CLI_DEBUG == 'true') {
11
14
  console.log(chalk.blue(typeof variable == 'object' ? JSON.stringify(variable) : variable));
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@aikidosec/ci-api-client",
3
- "version": "1.0.17",
3
+ "version": "1.0.18",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@aikidosec/ci-api-client",
9
- "version": "1.0.17",
9
+ "version": "1.0.18",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
- "axios": "1.16.1",
12
+ "axios": "1.18.0",
13
13
  "chalk": "5.3.0",
14
14
  "commander": "11.0.0",
15
15
  "configstore": "6.0.0",
@@ -116,8 +116,7 @@
116
116
  "node_modules/@types/node": {
117
117
  "version": "20.3.3",
118
118
  "dev": true,
119
- "license": "MIT",
120
- "peer": true
119
+ "license": "MIT"
121
120
  },
122
121
  "node_modules/abbrev": {
123
122
  "version": "1.1.1",
@@ -206,9 +205,9 @@
206
205
  "license": "MIT"
207
206
  },
208
207
  "node_modules/axios": {
209
- "version": "1.16.1",
210
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.1.tgz",
211
- "integrity": "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==",
208
+ "version": "1.18.0",
209
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.0.tgz",
210
+ "integrity": "sha512-E32NzpYKp++W7XRe52rHiXV2ehxmh3wbdgO7MHeFM+vqxLBYHzt0ElkiImtOBxtOmyp0yoC8C6uESVV84Y2/hw==",
212
211
  "license": "MIT",
213
212
  "dependencies": {
214
213
  "follow-redirects": "^1.16.0",
@@ -1271,7 +1270,6 @@
1271
1270
  "version": "5.1.6",
1272
1271
  "dev": true,
1273
1272
  "license": "Apache-2.0",
1274
- "peer": true,
1275
1273
  "bin": {
1276
1274
  "tsc": "bin/tsc",
1277
1275
  "tsserver": "bin/tsserver"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aikidosec/ci-api-client",
3
- "version": "1.0.17",
3
+ "version": "1.0.18",
4
4
  "description": "CLI api client to easily integrate the Aikido public CI API into custom deploy scripts",
5
5
  "license": "MIT",
6
6
  "author": "Bert Devriese <bert@builtinbruges.com>",
@@ -25,7 +25,7 @@
25
25
  }
26
26
  },
27
27
  "dependencies": {
28
- "axios": "1.16.1",
28
+ "axios": "1.18.0",
29
29
  "chalk": "5.3.0",
30
30
  "commander": "11.0.0",
31
31
  "configstore": "6.0.0",
package/src/aikidoApi.ts CHANGED
@@ -82,8 +82,26 @@ export type TPollIsScanningResult = {
82
82
  sbom_scan_completed?: boolean;
83
83
  };
84
84
 
85
+ export type TIssueSeverityBreakdown = {
86
+ critical: number;
87
+ high: number;
88
+ medium: number;
89
+ low: number;
90
+ };
91
+
92
+ export type TIssueBreakdown = {
93
+ sast: TIssueSeverityBreakdown;
94
+ sca: TIssueSeverityBreakdown;
95
+ iac: TIssueSeverityBreakdown;
96
+ secrets: TIssueSeverityBreakdown;
97
+ malware: TIssueSeverityBreakdown;
98
+ license: TIssueSeverityBreakdown;
99
+ code_quality: TIssueSeverityBreakdown;
100
+ };
101
+
85
102
  type TPollScanCompletedOptions = {
86
103
  gate_passed: boolean;
104
+ issue_breakdown?: TIssueBreakdown;
87
105
  };
88
106
 
89
107
  type TPollScanFeatureBranchCompletedOptions = {
@@ -245,7 +245,7 @@ export const scan = async ({
245
245
  const parseCliOptions = (userCliOptions: TScanUserCliOptions) => {
246
246
  // Version provided to the API corresponds with the version in package.json
247
247
  // of the cli client
248
- const apiOptions: TScanApiOptions = { version: '1.0.16' };
248
+ const apiOptions: TScanApiOptions = { version: '1.0.18' };
249
249
  const cliOptions: TScanCliOptions = { pollInterval: 5 };
250
250
 
251
251
  if (userCliOptions.pullRequestTitle) {
@@ -14,6 +14,7 @@ import { getApiKey } from '../configuration.js';
14
14
  import {
15
15
  outputError,
16
16
  outputHttpError,
17
+ outputJson,
17
18
  outputLog,
18
19
  startSpinner,
19
20
  } from '../output.js';
@@ -49,6 +50,7 @@ type TScanArguments = {
49
50
 
50
51
  type TScanCliOptions = {
51
52
  pollInterval: number;
53
+ json: boolean;
52
54
  };
53
55
 
54
56
  type TScanUserCliOptions = {
@@ -63,6 +65,7 @@ type TScanUserCliOptions = {
63
65
  pollInterval?: number;
64
66
  baseBranch?: string;
65
67
  slaMode?: boolean;
68
+ json?: boolean;
66
69
  };
67
70
 
68
71
  async function cli(
@@ -83,6 +86,13 @@ async function cli(
83
86
  let loader: Ora | null;
84
87
  let pollCount: number = 1;
85
88
 
89
+ // when the json option is added, only output the json result.
90
+ // Disabling console output takes care of the spinners and other extra info
91
+ const jsonOutput = cliOptions.json;
92
+ if (jsonOutput) {
93
+ process.env.QUIET = true;
94
+ }
95
+
86
96
  // Setup different scan() event handlers
87
97
  const onStart = () => {
88
98
  loader = startSpinner('Starting Aikido Security release scan');
@@ -107,6 +117,20 @@ async function cli(
107
117
  };
108
118
 
109
119
  const onScanComplete = (pollResult: any) => {
120
+ if (jsonOutput) {
121
+ outputJson({
122
+ gate_passed: pollResult.gate_passed === true,
123
+ issue_breakdown: pollResult.issue_breakdown,
124
+ });
125
+
126
+ if (pollResult.gate_passed !== true) {
127
+ // Don't process.exit() here: with piped stdout (CI) the JSON write
128
+ // may still be queued and a forced exit can truncate it.
129
+ process.exitCode = 10;
130
+ }
131
+ return;
132
+ }
133
+
110
134
  if (pollResult.gate_passed === true) {
111
135
  loader?.succeed('Scan completed, no open issues found');
112
136
  if (pollResult.diff_url) {
@@ -245,8 +269,8 @@ export const scan = async ({
245
269
  const parseCliOptions = (userCliOptions: TScanUserCliOptions) => {
246
270
  // Version provided to the API corresponds with the version in package.json
247
271
  // of the cli client
248
- const apiOptions: TScanApiOptions = { version: '1.0.16' };
249
- const cliOptions: TScanCliOptions = { pollInterval: 10 };
272
+ const apiOptions: TScanApiOptions = { version: '1.0.18' };
273
+ const cliOptions: TScanCliOptions = { pollInterval: 10, json: false };
250
274
 
251
275
  if (userCliOptions.pullRequestTitle) {
252
276
  apiOptions.pull_request_metadata = {
@@ -292,6 +316,9 @@ const parseCliOptions = (userCliOptions: TScanUserCliOptions) => {
292
316
  } else if (userCliOptions.pollInterval) {
293
317
  cliOptions.pollInterval = userCliOptions.pollInterval;
294
318
  }
319
+ if (userCliOptions.json) {
320
+ cliOptions.json = true;
321
+ }
295
322
 
296
323
  return { apiOptions, cliOptions };
297
324
  };
@@ -363,6 +390,10 @@ export const cliSetup = (program: Command) =>
363
390
  '--sla-mode',
364
391
  'Let Aikido only fail only on open issues that have gone out of SLA.'
365
392
  )
393
+ .option(
394
+ '--json',
395
+ 'Output the scan result as JSON (gate_passed and issue_breakdown)'
396
+ )
366
397
  .description('Run a release scan of an Aikido repo.')
367
398
  .action(cli);
368
399
 
package/src/index.ts CHANGED
@@ -20,7 +20,7 @@ program
20
20
  .description(
21
21
  'CLI api client to easily integrate the Aikido public CI API into custom deploy scripts'
22
22
  )
23
- .version('1.0.16');
23
+ .version('1.0.18');
24
24
 
25
25
  // Load in all app commands and set them up in the `program` instance
26
26
  apiKey.cliSetup(program);
package/src/output.ts CHANGED
@@ -10,6 +10,13 @@ export const outputLog = (message: string): void => {
10
10
  }
11
11
  };
12
12
 
13
+ // Output a machine readable result
14
+ // This ignores the global "quiet" option: it is the output that was asked
15
+ // for, not human readable progress information
16
+ export const outputJson = (data: unknown): void => {
17
+ console.log(JSON.stringify(data, null, 2));
18
+ };
19
+
13
20
  // Output some message, var, .. if --debug was provided
14
21
  export const outputDebug = (variable: any): void => {
15
22
  if (process.env.CLI_DEBUG == true || process.env.CLI_DEBUG == 'true') {