@aikidosec/ci-api-client 1.0.16 → 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/.env CHANGED
@@ -1,3 +1,4 @@
1
1
  AIKIDO_API=https://app.aikido.dev
2
2
  AIKIDO_US_API=https://app.us.aikido.dev
3
3
  AIKIDO_ME_API=https://app.me.aikido.dev
4
+ AIKIDO_AU_API=https://app.au.aikido.dev
@@ -9,7 +9,7 @@ permissions:
9
9
 
10
10
  jobs:
11
11
  build:
12
- runs-on: ubuntu-latest
12
+ runs-on: open-source-releaser
13
13
  permissions:
14
14
  contents: read
15
15
  id-token: write
@@ -18,13 +18,13 @@ jobs:
18
18
  - uses: actions/checkout@v4
19
19
  - uses: actions/setup-node@v4
20
20
  with:
21
- node-version: "22"
21
+ node-version: "22.14.0"
22
22
  registry-url: "https://registry.npmjs.org"
23
23
  scope: "@aikidosec"
24
24
  cache: 'npm'
25
25
 
26
26
  - name: Update npm
27
- run: npm install -g npm@11.10.0 # pinned to 11.10.0 to work around https://github.com/nodejs/node/issues/62425
27
+ run: npm install -g npm@11.15.0
28
28
 
29
29
  - name: Install dependencies
30
30
  run: npm ci
@@ -42,4 +42,4 @@ jobs:
42
42
  - name: Publish to NPM
43
43
  run: |
44
44
  echo "Publishing version ${{ steps.get_version.outputs.tag }} to NPM"
45
- npm publish --access public
45
+ npm stage publish --access public --verbose
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;
package/lib/aikidoApi.js CHANGED
@@ -9,6 +9,9 @@ const getApiUrl = () => {
9
9
  if (apiKey.startsWith('AIK_CI_ME_')) {
10
10
  return process.env.AIKIDO_ME_API || 'https://app.me.aikido.dev';
11
11
  }
12
+ if (apiKey.startsWith('AIK_CI_AU_')) {
13
+ return process.env.AIKIDO_AU_API || 'https://app.au.aikido.dev';
14
+ }
12
15
  return process.env.AIKIDO_API || 'https://app.aikido.dev';
13
16
  };
14
17
  const getApiHeaders = () => ({
@@ -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.16",
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.16",
9
+ "version": "1.0.18",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
- "axios": "1.8.3",
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",
@@ -143,6 +142,35 @@
143
142
  "node": ">=0.4.0"
144
143
  }
145
144
  },
145
+ "node_modules/agent-base": {
146
+ "version": "6.0.2",
147
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
148
+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
149
+ "license": "MIT",
150
+ "dependencies": {
151
+ "debug": "4"
152
+ },
153
+ "engines": {
154
+ "node": ">= 6.0.0"
155
+ }
156
+ },
157
+ "node_modules/agent-base/node_modules/debug": {
158
+ "version": "4.4.3",
159
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
160
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
161
+ "license": "MIT",
162
+ "dependencies": {
163
+ "ms": "^2.1.3"
164
+ },
165
+ "engines": {
166
+ "node": ">=6.0"
167
+ },
168
+ "peerDependenciesMeta": {
169
+ "supports-color": {
170
+ "optional": true
171
+ }
172
+ }
173
+ },
146
174
  "node_modules/ansi-regex": {
147
175
  "version": "6.0.1",
148
176
  "license": "MIT",
@@ -172,17 +200,20 @@
172
200
  },
173
201
  "node_modules/asynckit": {
174
202
  "version": "0.4.0",
203
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
204
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
175
205
  "license": "MIT"
176
206
  },
177
207
  "node_modules/axios": {
178
- "version": "1.8.3",
179
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.3.tgz",
180
- "integrity": "sha512-iP4DebzoNlP/YN2dpwCgb8zoCmhtkajzS48JvwmkSkXvPI3DHc7m+XYL5tGnSlJtR6nImXZmdCuN5aP8dh1d8A==",
208
+ "version": "1.18.0",
209
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.0.tgz",
210
+ "integrity": "sha512-E32NzpYKp++W7XRe52rHiXV2ehxmh3wbdgO7MHeFM+vqxLBYHzt0ElkiImtOBxtOmyp0yoC8C6uESVV84Y2/hw==",
181
211
  "license": "MIT",
182
212
  "dependencies": {
183
- "follow-redirects": "^1.15.6",
184
- "form-data": "^4.0.0",
185
- "proxy-from-env": "^1.1.0"
213
+ "follow-redirects": "^1.16.0",
214
+ "form-data": "^4.0.5",
215
+ "https-proxy-agent": "^5.0.1",
216
+ "proxy-from-env": "^2.1.0"
186
217
  }
187
218
  },
188
219
  "node_modules/balanced-match": {
@@ -348,6 +379,8 @@
348
379
  },
349
380
  "node_modules/combined-stream": {
350
381
  "version": "1.0.8",
382
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
383
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
351
384
  "license": "MIT",
352
385
  "dependencies": {
353
386
  "delayed-stream": "~1.0.0"
@@ -423,6 +456,8 @@
423
456
  },
424
457
  "node_modules/delayed-stream": {
425
458
  "version": "1.0.0",
459
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
460
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
426
461
  "license": "MIT",
427
462
  "engines": {
428
463
  "node": ">=0.4.0"
@@ -492,9 +527,9 @@
492
527
  }
493
528
  },
494
529
  "node_modules/es-object-atoms": {
495
- "version": "1.1.1",
496
- "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
497
- "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
530
+ "version": "1.1.2",
531
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
532
+ "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
498
533
  "license": "MIT",
499
534
  "dependencies": {
500
535
  "es-errors": "^1.3.0"
@@ -530,7 +565,9 @@
530
565
  }
531
566
  },
532
567
  "node_modules/follow-redirects": {
533
- "version": "1.15.6",
568
+ "version": "1.16.0",
569
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
570
+ "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
534
571
  "funding": [
535
572
  {
536
573
  "type": "individual",
@@ -548,16 +585,16 @@
548
585
  }
549
586
  },
550
587
  "node_modules/form-data": {
551
- "version": "4.0.4",
552
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
553
- "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
588
+ "version": "4.0.6",
589
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz",
590
+ "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==",
554
591
  "license": "MIT",
555
592
  "dependencies": {
556
593
  "asynckit": "^0.4.0",
557
594
  "combined-stream": "^1.0.8",
558
595
  "es-set-tostringtag": "^2.1.0",
559
- "hasown": "^2.0.2",
560
- "mime-types": "^2.1.12"
596
+ "hasown": "^2.0.4",
597
+ "mime-types": "^2.1.35"
561
598
  },
562
599
  "engines": {
563
600
  "node": ">= 6"
@@ -684,9 +721,9 @@
684
721
  }
685
722
  },
686
723
  "node_modules/hasown": {
687
- "version": "2.0.2",
688
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
689
- "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
724
+ "version": "2.0.4",
725
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
726
+ "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
690
727
  "license": "MIT",
691
728
  "dependencies": {
692
729
  "function-bind": "^1.1.2"
@@ -695,6 +732,36 @@
695
732
  "node": ">= 0.4"
696
733
  }
697
734
  },
735
+ "node_modules/https-proxy-agent": {
736
+ "version": "5.0.1",
737
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
738
+ "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
739
+ "license": "MIT",
740
+ "dependencies": {
741
+ "agent-base": "6",
742
+ "debug": "4"
743
+ },
744
+ "engines": {
745
+ "node": ">= 6"
746
+ }
747
+ },
748
+ "node_modules/https-proxy-agent/node_modules/debug": {
749
+ "version": "4.4.3",
750
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
751
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
752
+ "license": "MIT",
753
+ "dependencies": {
754
+ "ms": "^2.1.3"
755
+ },
756
+ "engines": {
757
+ "node": ">=6.0"
758
+ },
759
+ "peerDependenciesMeta": {
760
+ "supports-color": {
761
+ "optional": true
762
+ }
763
+ }
764
+ },
698
765
  "node_modules/ieee754": {
699
766
  "version": "1.2.1",
700
767
  "funding": [
@@ -828,6 +895,8 @@
828
895
  },
829
896
  "node_modules/mime-db": {
830
897
  "version": "1.52.0",
898
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
899
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
831
900
  "license": "MIT",
832
901
  "engines": {
833
902
  "node": ">= 0.6"
@@ -835,6 +904,8 @@
835
904
  },
836
905
  "node_modules/mime-types": {
837
906
  "version": "2.1.35",
907
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
908
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
838
909
  "license": "MIT",
839
910
  "dependencies": {
840
911
  "mime-db": "1.52.0"
@@ -863,7 +934,6 @@
863
934
  },
864
935
  "node_modules/ms": {
865
936
  "version": "2.1.3",
866
- "dev": true,
867
937
  "license": "MIT"
868
938
  },
869
939
  "node_modules/nodemon": {
@@ -972,8 +1042,13 @@
972
1042
  }
973
1043
  },
974
1044
  "node_modules/proxy-from-env": {
975
- "version": "1.1.0",
976
- "license": "MIT"
1045
+ "version": "2.1.0",
1046
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
1047
+ "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
1048
+ "license": "MIT",
1049
+ "engines": {
1050
+ "node": ">=10"
1051
+ }
977
1052
  },
978
1053
  "node_modules/pstree.remy": {
979
1054
  "version": "1.1.8",
@@ -1195,7 +1270,6 @@
1195
1270
  "version": "5.1.6",
1196
1271
  "dev": true,
1197
1272
  "license": "Apache-2.0",
1198
- "peer": true,
1199
1273
  "bin": {
1200
1274
  "tsc": "bin/tsc",
1201
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.16",
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.8.3",
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
@@ -13,6 +13,10 @@ const getApiUrl = () => {
13
13
  return process.env.AIKIDO_ME_API || 'https://app.me.aikido.dev';
14
14
  }
15
15
 
16
+ if (apiKey.startsWith('AIK_CI_AU_')) {
17
+ return process.env.AIKIDO_AU_API || 'https://app.au.aikido.dev';
18
+ }
19
+
16
20
  return process.env.AIKIDO_API || 'https://app.aikido.dev';
17
21
  };
18
22
  const getApiHeaders = () => ({
@@ -78,8 +82,26 @@ export type TPollIsScanningResult = {
78
82
  sbom_scan_completed?: boolean;
79
83
  };
80
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
+
81
102
  type TPollScanCompletedOptions = {
82
103
  gate_passed: boolean;
104
+ issue_breakdown?: TIssueBreakdown;
83
105
  };
84
106
 
85
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') {