@aikidosec/ci-api-client 1.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/.editorconfig +18 -0
- package/.env +1 -0
- package/.prettierrc +10 -0
- package/CONTRIBUTING.md +30 -0
- package/LICENSE +21 -0
- package/README.md +78 -0
- package/RELEASING.md +26 -0
- package/lib/aikidoApi.d.ts +60 -0
- package/lib/aikidoApi.js +52 -0
- package/lib/commands/apiKey.d.ts +8 -0
- package/lib/commands/apiKey.js +18 -0
- package/lib/commands/scan.d.ts +36 -0
- package/lib/commands/scan.js +188 -0
- package/lib/commands/upload.d.ts +23 -0
- package/lib/commands/upload.js +94 -0
- package/lib/configuration.d.ts +4 -0
- package/lib/configuration.js +13 -0
- package/lib/index.d.ts +13 -0
- package/lib/index.js +39 -0
- package/lib/output.d.ts +7 -0
- package/lib/output.js +53 -0
- package/package.json +51 -0
- package/src/aikidoApi.ts +148 -0
- package/src/commands/apiKey.ts +30 -0
- package/src/commands/scan.ts +371 -0
- package/src/commands/upload.ts +184 -0
- package/src/configuration.ts +21 -0
- package/src/index.ts +74 -0
- package/src/output.ts +87 -0
- package/tsconfig.json +27 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { Option } from 'commander';
|
|
2
|
+
import { TUploadPayloadType, uploadCustomScanResult, } from '../aikidoApi.js';
|
|
3
|
+
import { getApiKey } from '../configuration.js';
|
|
4
|
+
import { outputError, outputHttpError, startSpinner, } from '../output.js';
|
|
5
|
+
import { readFile } from 'fs';
|
|
6
|
+
import { promisify } from 'util';
|
|
7
|
+
async function cli(options, command) {
|
|
8
|
+
const apiKey = getApiKey();
|
|
9
|
+
if (!apiKey) {
|
|
10
|
+
outputError('Please set an api key using: aikido-cli apikey <key>');
|
|
11
|
+
}
|
|
12
|
+
const { apiOptions, cliOptions } = await parseCliOptions(options);
|
|
13
|
+
let loader = startSpinner('Uploading custom scan');
|
|
14
|
+
const onUploadComplete = (result) => {
|
|
15
|
+
if (result.success === 1) {
|
|
16
|
+
loader?.succeed('Upload completed');
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
loader?.fail('Upload failed, please verify your payload data and try again');
|
|
20
|
+
process.exit(10);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
const onUploadFail = (error) => {
|
|
24
|
+
loader?.fail();
|
|
25
|
+
outputHttpError(error);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
};
|
|
28
|
+
await upload({
|
|
29
|
+
data: apiOptions,
|
|
30
|
+
onUploadComplete,
|
|
31
|
+
onUploadFail,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
export const upload = async ({ data, onUploadComplete, onUploadFail, }) => {
|
|
35
|
+
try {
|
|
36
|
+
const result = await uploadCustomScanResult(data);
|
|
37
|
+
onUploadComplete?.(result);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
onUploadFail?.(error);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
const parseCliOptions = async (userCliOptions) => {
|
|
45
|
+
const apiOptions = {
|
|
46
|
+
repository_id: userCliOptions.repositoryId,
|
|
47
|
+
container_image_name: userCliOptions.containerImage ?? undefined,
|
|
48
|
+
payload_type: userCliOptions.type,
|
|
49
|
+
payload: '',
|
|
50
|
+
};
|
|
51
|
+
const cliOptions = {
|
|
52
|
+
payloadFile: userCliOptions.file,
|
|
53
|
+
payload: userCliOptions.payload,
|
|
54
|
+
};
|
|
55
|
+
if (userCliOptions.type == TUploadPayloadType.JsonSbom) {
|
|
56
|
+
if (userCliOptions.containerImage === undefined ||
|
|
57
|
+
userCliOptions.containerImage.trim() === '') {
|
|
58
|
+
outputError('Please provide a valid container image name with --container-image');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (userCliOptions.file) {
|
|
62
|
+
try {
|
|
63
|
+
const data = await promisify(readFile)(userCliOptions.file, 'utf8');
|
|
64
|
+
apiOptions.payload = data;
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
outputError('Cannot read file provided with --payloadFile, aborting');
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
else if (cliOptions.payload?.trim() != '') {
|
|
72
|
+
apiOptions.payload = cliOptions.payload;
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
outputError('No payload provided, aborting');
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
return { apiOptions, cliOptions };
|
|
79
|
+
};
|
|
80
|
+
const requiredOption = (option) => {
|
|
81
|
+
option.required = true;
|
|
82
|
+
return option;
|
|
83
|
+
};
|
|
84
|
+
export const cliSetup = (program) => program
|
|
85
|
+
.command('upload')
|
|
86
|
+
.option('-s, --scan-id <scanid>', 'Set the scan id')
|
|
87
|
+
.requiredOption('-r, --repository-id <repositoryid>', 'Set the repository id')
|
|
88
|
+
.option('-i, --container-image <container-image-name>', 'Set the container image name. ')
|
|
89
|
+
.addOption(requiredOption(new Option('-t, --type <type>', 'Set the provided payload type. Available options are: checkov, json-sbom').choices(['checkov', 'json-sbom'])))
|
|
90
|
+
.option('-f, --file <payloadFile>', 'Specify the payload file. The cli command wil read the file and use the contents as payload data.')
|
|
91
|
+
.option('-p, --payload <payload>', 'Set the payload')
|
|
92
|
+
.description('Upload a custom scan result.')
|
|
93
|
+
.action(cli);
|
|
94
|
+
export default { cli, cliSetup };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import Configstore from 'configstore';
|
|
2
|
+
import { program } from './index.js';
|
|
3
|
+
export const getConfigStore = () => {
|
|
4
|
+
return new Configstore(program.name());
|
|
5
|
+
};
|
|
6
|
+
export const getApiKey = () => {
|
|
7
|
+
return program.getOptionValue('apikey')
|
|
8
|
+
? program.getOptionValue('apikey')
|
|
9
|
+
: getConfigStore().get('apikey');
|
|
10
|
+
};
|
|
11
|
+
export const setApiKey = (newApiKey) => {
|
|
12
|
+
return getConfigStore().set('apikey', newApiKey);
|
|
13
|
+
};
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
export declare const pkgJson: any;
|
|
4
|
+
export declare const program: Command;
|
|
5
|
+
declare global {
|
|
6
|
+
namespace NodeJS {
|
|
7
|
+
interface ProcessEnv {
|
|
8
|
+
[key: string]: any;
|
|
9
|
+
QUIET?: boolean;
|
|
10
|
+
CLI_DEBUG?: boolean | string;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import dotenv from 'dotenv';
|
|
5
|
+
import { readFileSync } from 'fs';
|
|
6
|
+
import { dirname, join } from 'path';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
import apiKey from './commands/apiKey.js';
|
|
9
|
+
import scan from './commands/scan.js';
|
|
10
|
+
import upload from './commands/upload.js';
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = dirname(__filename);
|
|
13
|
+
const __rootdirname = dirname(__dirname);
|
|
14
|
+
dotenv.config(process.env.NODE_ENV ? { path: `.env.${process.env.NODE_ENV}` } : {});
|
|
15
|
+
const pkgJsonPath = join('/', __rootdirname, 'package.json');
|
|
16
|
+
export const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf8'));
|
|
17
|
+
export const program = new Command();
|
|
18
|
+
program
|
|
19
|
+
.name(pkgJson.name)
|
|
20
|
+
.description(pkgJson.description)
|
|
21
|
+
.version(pkgJson.version);
|
|
22
|
+
apiKey.cliSetup(program);
|
|
23
|
+
scan.cliSetup(program);
|
|
24
|
+
upload.cliSetup(program);
|
|
25
|
+
program
|
|
26
|
+
.option('-q, --quiet', 'Disable console output when executing commands')
|
|
27
|
+
.on('option:quiet', function () {
|
|
28
|
+
process.env.QUIET = true;
|
|
29
|
+
});
|
|
30
|
+
program
|
|
31
|
+
.option('--debug', 'Add additional debug information to command output')
|
|
32
|
+
.on('option:debug', function () {
|
|
33
|
+
process.env.CLI_DEBUG = true;
|
|
34
|
+
});
|
|
35
|
+
program.option('--apikey <apikey>', 'Use a cli apikey instead of reading the key from the configuration');
|
|
36
|
+
program.configureOutput({
|
|
37
|
+
outputError: (str, write) => write(chalk.red(str)),
|
|
38
|
+
});
|
|
39
|
+
program.parse();
|
package/lib/output.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Ora } from 'ora';
|
|
2
|
+
import { AxiosError } from 'axios';
|
|
3
|
+
export declare const outputLog: (message: string) => void;
|
|
4
|
+
export declare const outputDebug: (variable: any) => void;
|
|
5
|
+
export declare const outputError: (message: string, exitCode?: number) => void;
|
|
6
|
+
export declare const outputHttpError: (axiosError: AxiosError) => void;
|
|
7
|
+
export declare const startSpinner: (message: string) => Ora | null;
|
package/lib/output.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import ora from 'ora';
|
|
2
|
+
import { program } from './index.js';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
export const outputLog = (message) => {
|
|
5
|
+
if (!process.env.QUIET) {
|
|
6
|
+
console.log(message);
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
export const outputDebug = (variable) => {
|
|
10
|
+
if (process.env.CLI_DEBUG == true || process.env.CLI_DEBUG == 'true') {
|
|
11
|
+
console.log(chalk.blue(typeof variable == 'object' ? JSON.stringify(variable) : variable));
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
export const outputError = (message, exitCode = 1) => {
|
|
15
|
+
program.error(message, {
|
|
16
|
+
exitCode,
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
export const outputHttpError = (axiosError) => {
|
|
20
|
+
if (axiosError.code === 'ECONNREFUSED') {
|
|
21
|
+
outputError(`Could not connect to Aikido API (${axiosError.message}). Please verify your network settings`);
|
|
22
|
+
}
|
|
23
|
+
else if (axiosError.response?.status &&
|
|
24
|
+
axiosError.response?.status === 401) {
|
|
25
|
+
outputError('Request failed. The provided api key is most likely no longer valid and has been rotated or revoked. Visit https://app.aikido.dev/settings/integrations/continuous-integration to generate a new key.');
|
|
26
|
+
}
|
|
27
|
+
else if (axiosError.response?.status &&
|
|
28
|
+
axiosError.response?.status === 403) {
|
|
29
|
+
outputError('Could not authenticate with the Aikido API. Please verify your Aikdio API key.');
|
|
30
|
+
}
|
|
31
|
+
else if (axiosError.response?.status &&
|
|
32
|
+
axiosError.response?.status === 500) {
|
|
33
|
+
outputError('Something went wrong contacting the Aikido API.');
|
|
34
|
+
}
|
|
35
|
+
const statusStr = axiosError.response?.status
|
|
36
|
+
? ` (${axiosError.response?.status})`
|
|
37
|
+
: '';
|
|
38
|
+
if (axiosError.response) {
|
|
39
|
+
outputDebug(axiosError.response?.status);
|
|
40
|
+
outputDebug(axiosError.response?.headers);
|
|
41
|
+
outputDebug(axiosError.response?.data);
|
|
42
|
+
}
|
|
43
|
+
outputError(`Something unexpected went wrong${statusStr}... Please contact us if this problem persists.`);
|
|
44
|
+
};
|
|
45
|
+
export const startSpinner = (message) => {
|
|
46
|
+
if (process.env.QUIET) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
const spinner = ora(message);
|
|
50
|
+
spinner.color = 'magenta';
|
|
51
|
+
spinner.start();
|
|
52
|
+
return spinner;
|
|
53
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aikidosec/ci-api-client",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI api client to easily integrate the Aikido public CI API into custom deploy scripts",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Bert Devriese <bert@builtinbruges.com>",
|
|
7
|
+
"private": false,
|
|
8
|
+
"type": "module",
|
|
9
|
+
"main": "lib/index.js",
|
|
10
|
+
"bin": {
|
|
11
|
+
"aikido-api-client": "./lib/index.js"
|
|
12
|
+
},
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/AikidoSec/api-client.git"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"start": "nodemon --watch 'src/**/*.ts' --esm --exec 'ts-node' src/index.ts",
|
|
19
|
+
"build": "tsc -p .",
|
|
20
|
+
"local": "yarn build && yarn global add file:$PWD; aikido-api-client",
|
|
21
|
+
"local-rm": "yarn global remove aikido-api-client",
|
|
22
|
+
"format": "prettier --write 'src/**/*.ts'",
|
|
23
|
+
"//": {
|
|
24
|
+
"refresh": "rm -rf ./node_modules ./yarn.lock && yarn"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"axios": "^1.4.0",
|
|
29
|
+
"chalk": "^5.3.0",
|
|
30
|
+
"commander": "^11.0.0",
|
|
31
|
+
"configstore": "^6.0.0",
|
|
32
|
+
"dotenv": "^16.3.1",
|
|
33
|
+
"ora": "^6.3.1"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/chalk": "^2.2.0",
|
|
37
|
+
"@types/commander": "^2.12.2",
|
|
38
|
+
"@types/configstore": "^6.0.0",
|
|
39
|
+
"@types/dotenv": "^8.2.0",
|
|
40
|
+
"@types/node": "^20.3.3",
|
|
41
|
+
"nodemon": "^2.0.22",
|
|
42
|
+
"prettier": "^2.8.8",
|
|
43
|
+
"ts-node": "^10.9.1",
|
|
44
|
+
"typescript": "^5.1.6"
|
|
45
|
+
},
|
|
46
|
+
"keywords": [
|
|
47
|
+
"aikido",
|
|
48
|
+
"security",
|
|
49
|
+
"cli"
|
|
50
|
+
]
|
|
51
|
+
}
|
package/src/aikidoApi.ts
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import { getApiKey } from './configuration.js';
|
|
3
|
+
import { outputDebug } from './output.js';
|
|
4
|
+
|
|
5
|
+
const getApiUrl = () => process.env.AIKIDO_API || 'https://app.aikido.dev';
|
|
6
|
+
const getApiHeaders = () => ({
|
|
7
|
+
'Content-Type': 'application/json',
|
|
8
|
+
'X-AIK-API-SECRET': getApiKey(),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
// #region Scan start
|
|
12
|
+
export type TScanApiOptions = {
|
|
13
|
+
repository_id?: string | number;
|
|
14
|
+
base_commit_id?: string;
|
|
15
|
+
head_commit_id?: string;
|
|
16
|
+
branch_name?: string;
|
|
17
|
+
pull_request_metadata?: {
|
|
18
|
+
title?: string;
|
|
19
|
+
url?: string;
|
|
20
|
+
};
|
|
21
|
+
self_managed_scanners?: string[];
|
|
22
|
+
fail_on_dependency_scan?: boolean;
|
|
23
|
+
fail_on_sast_scan?: boolean;
|
|
24
|
+
fail_on_iac_scan?: boolean;
|
|
25
|
+
minimum_severity?: string;
|
|
26
|
+
version: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type TStartScanResult = {
|
|
30
|
+
scan_id: number;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Start a scan with options (see: startScanSchema)
|
|
34
|
+
// and return the data
|
|
35
|
+
export async function startScan(
|
|
36
|
+
data: TScanApiOptions
|
|
37
|
+
): Promise<TStartScanResult> {
|
|
38
|
+
const requestUrl = `${getApiUrl()}/api/integrations/continuous_integration/scan/repository`;
|
|
39
|
+
const requestConfig = {
|
|
40
|
+
method: 'POST',
|
|
41
|
+
data,
|
|
42
|
+
headers: getApiHeaders(),
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
outputDebug(`API request: ${requestUrl}`);
|
|
46
|
+
outputDebug(requestConfig);
|
|
47
|
+
|
|
48
|
+
const resultData = (await axios(requestUrl, requestConfig)).data;
|
|
49
|
+
outputDebug(resultData);
|
|
50
|
+
|
|
51
|
+
return resultData;
|
|
52
|
+
}
|
|
53
|
+
// #endregion
|
|
54
|
+
|
|
55
|
+
// #region Scan polling
|
|
56
|
+
export type TPollIsScanningResult = {
|
|
57
|
+
all_scans_completed: boolean;
|
|
58
|
+
dependency_scan_completed: boolean;
|
|
59
|
+
sast_scan_completed: boolean;
|
|
60
|
+
iac_scan_completed: boolean;
|
|
61
|
+
sbom_scan_completed?: boolean;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
type TPollScanCompletedOptions = {
|
|
65
|
+
gate_passed: boolean;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
type TPollScanFeatureBranchCompletedOptions = {
|
|
69
|
+
new_issues_found: number;
|
|
70
|
+
new_dependency_issues_found: number;
|
|
71
|
+
new_sast_issues_found: number;
|
|
72
|
+
new_iac_issues_found: number;
|
|
73
|
+
diff_url: string;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
type TPollScanDefaultBranchCompletedOptions = {
|
|
77
|
+
open_issues_found: number;
|
|
78
|
+
issue_links: string[];
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export type TPollScanFeatureBranchCompletedResult = TPollIsScanningResult &
|
|
82
|
+
TPollScanCompletedOptions &
|
|
83
|
+
TPollScanFeatureBranchCompletedOptions;
|
|
84
|
+
|
|
85
|
+
export type TPollScanCompletedDefaultBranchResult = TPollIsScanningResult &
|
|
86
|
+
TPollScanCompletedOptions &
|
|
87
|
+
TPollScanDefaultBranchCompletedOptions;
|
|
88
|
+
|
|
89
|
+
export async function pollScanStatus(
|
|
90
|
+
scanId: number
|
|
91
|
+
): Promise<
|
|
92
|
+
| TPollIsScanningResult
|
|
93
|
+
| TPollScanFeatureBranchCompletedResult
|
|
94
|
+
| TPollScanCompletedDefaultBranchResult
|
|
95
|
+
> {
|
|
96
|
+
const requestUrl = `${getApiUrl()}/api/integrations/continuous_integration/scan/repository`;
|
|
97
|
+
const requestConfig = {
|
|
98
|
+
method: 'GET',
|
|
99
|
+
params: { scan_id: scanId },
|
|
100
|
+
headers: getApiHeaders(),
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
outputDebug(`API request: ${requestUrl}`);
|
|
104
|
+
outputDebug(requestConfig);
|
|
105
|
+
|
|
106
|
+
const resultData = (await axios(requestUrl, requestConfig)).data;
|
|
107
|
+
outputDebug(resultData);
|
|
108
|
+
|
|
109
|
+
return resultData;
|
|
110
|
+
}
|
|
111
|
+
// #endregion
|
|
112
|
+
|
|
113
|
+
// #region Upload custom result
|
|
114
|
+
export enum TUploadPayloadType {
|
|
115
|
+
Checkov = 'checkov',
|
|
116
|
+
JsonSbom = 'json-sbom',
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export type TUploadApiOptions = {
|
|
120
|
+
scan_id?: number;
|
|
121
|
+
repository_id: string | number;
|
|
122
|
+
container_image_name?: string;
|
|
123
|
+
payload_type: TUploadPayloadType;
|
|
124
|
+
payload: string;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export type TUploadResult = {
|
|
128
|
+
success: number;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export async function uploadCustomScanResult(
|
|
132
|
+
data: TUploadApiOptions
|
|
133
|
+
): Promise<TUploadResult> {
|
|
134
|
+
const requestUrl = `${getApiUrl()}/api/integrations/continuous_integration/scan/custom`;
|
|
135
|
+
const requestConfig = {
|
|
136
|
+
method: 'POST',
|
|
137
|
+
data,
|
|
138
|
+
headers: getApiHeaders(),
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
outputDebug(`API request: ${requestUrl}`);
|
|
142
|
+
outputDebug(requestConfig);
|
|
143
|
+
|
|
144
|
+
const resultData = (await axios(requestUrl, requestConfig)).data;
|
|
145
|
+
outputDebug(resultData);
|
|
146
|
+
|
|
147
|
+
return resultData;
|
|
148
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Argument, Command } from 'commander';
|
|
2
|
+
import { setApiKey } from '../configuration.js';
|
|
3
|
+
import { outputError, outputLog } from '../output.js';
|
|
4
|
+
|
|
5
|
+
function cli(apiKey: string): void {
|
|
6
|
+
if (apiKey && apiKey.trim().length != 128) {
|
|
7
|
+
outputError('That does not seem right, please verify your api key');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
setApiKey(apiKey);
|
|
11
|
+
outputLog(
|
|
12
|
+
apiKey
|
|
13
|
+
? 'Your new Aikido API key has been set'
|
|
14
|
+
: 'Your Aikido API key has been removed'
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const cliSetup = (program: Command): Command =>
|
|
19
|
+
program
|
|
20
|
+
.command('apikey')
|
|
21
|
+
.addArgument(
|
|
22
|
+
new Argument(
|
|
23
|
+
'<key>',
|
|
24
|
+
'Your Aikido public CI API key. See also: https://app.aikido.dev/settings/integrations/continuous-integration. To remove your apikey, leave the <key> argument value empty.'
|
|
25
|
+
).argOptional()
|
|
26
|
+
)
|
|
27
|
+
.description('Set (or remove) your Aikido public CI API key.')
|
|
28
|
+
.action(cli);
|
|
29
|
+
|
|
30
|
+
export default { cli, cliSetup };
|