@socketsecurity/cli 0.9.0 → 0.10.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.
Files changed (38) hide show
  1. package/README.md +5 -0
  2. package/cli.js +12 -1
  3. package/lib/commands/audit-log/index.js +162 -0
  4. package/lib/commands/cdxgen/index.js +211 -0
  5. package/lib/commands/dependencies/index.js +150 -0
  6. package/lib/commands/index.js +11 -3
  7. package/lib/commands/info/index.js +123 -81
  8. package/lib/commands/login/index.js +1 -1
  9. package/lib/commands/logout/index.js +1 -1
  10. package/lib/commands/npm/index.js +8 -3
  11. package/lib/commands/npx/index.js +1 -1
  12. package/lib/commands/raw-npm/index.js +59 -0
  13. package/lib/commands/raw-npx/index.js +59 -0
  14. package/lib/commands/report/create.js +1 -1
  15. package/lib/commands/report/index.js +1 -1
  16. package/lib/commands/report/view.js +1 -1
  17. package/lib/commands/repos/create.js +166 -0
  18. package/lib/commands/repos/delete.js +93 -0
  19. package/lib/commands/repos/index.js +30 -0
  20. package/lib/commands/repos/list.js +170 -0
  21. package/lib/commands/repos/update.js +166 -0
  22. package/lib/commands/repos/view.js +128 -0
  23. package/lib/commands/scan/create.js +245 -0
  24. package/lib/commands/scan/delete.js +112 -0
  25. package/lib/commands/scan/index.js +30 -0
  26. package/lib/commands/scan/list.js +192 -0
  27. package/lib/commands/scan/metadata.js +113 -0
  28. package/lib/commands/scan/stream.js +115 -0
  29. package/lib/commands/wrapper/index.js +199 -0
  30. package/lib/flags/command.js +14 -0
  31. package/lib/flags/index.js +1 -0
  32. package/lib/shadow/npm-injection.cjs +11 -1
  33. package/lib/utils/format-issues.js +28 -1
  34. package/lib/utils/meow-with-subcommands.js +1 -2
  35. package/lib/utils/misc.js +0 -1
  36. package/lib/utils/path-resolve.js +31 -6
  37. package/lib/utils/sdk.js +0 -3
  38. package/package.json +79 -62
@@ -0,0 +1,128 @@
1
+ /* eslint-disable no-console */
2
+
3
+ import chalk from 'chalk'
4
+ // @ts-ignore
5
+ import chalkTable from 'chalk-table'
6
+ import meow from 'meow'
7
+ import ora from 'ora'
8
+
9
+ import { outputFlags } from '../../flags/index.js'
10
+ import { handleApiCall, handleUnsuccessfulApiResponse } from '../../utils/api-helpers.js'
11
+ import { printFlagList } from '../../utils/formatting.js'
12
+ import { getDefaultKey, setupSdk } from '../../utils/sdk.js'
13
+
14
+ /** @type {import('../../utils/meow-with-subcommands.js').CliSubcommand} */
15
+ export const view = {
16
+ description: 'View repositories in an organization',
17
+ async run (argv, importMeta, { parentName }) {
18
+ const name = parentName + ' view'
19
+
20
+ const input = setupCommand(name, view.description, argv, importMeta)
21
+ if (input) {
22
+ const spinnerText = 'Fetching repository... \n'
23
+ const spinner = ora(spinnerText).start()
24
+ await viewRepository(input.orgSlug, input.repositoryName, spinner)
25
+ }
26
+ }
27
+ }
28
+
29
+ // Internal functions
30
+
31
+ /**
32
+ * @typedef CommandContext
33
+ * @property {boolean} outputJson
34
+ * @property {boolean} outputMarkdown
35
+ * @property {string} orgSlug
36
+ * @property {string} repositoryName
37
+ */
38
+
39
+ /**
40
+ * @param {string} name
41
+ * @param {string} description
42
+ * @param {readonly string[]} argv
43
+ * @param {ImportMeta} importMeta
44
+ * @returns {void|CommandContext}
45
+ */
46
+ function setupCommand (name, description, argv, importMeta) {
47
+ const flags = {
48
+ ...outputFlags
49
+ }
50
+
51
+ const cli = meow(`
52
+ Usage
53
+ $ ${name} <org slug>
54
+
55
+ Options
56
+ ${printFlagList(flags, 6)}
57
+
58
+ Examples
59
+ $ ${name} FakeOrg
60
+ `, {
61
+ argv,
62
+ description,
63
+ importMeta,
64
+ flags
65
+ })
66
+
67
+ const {
68
+ json: outputJson,
69
+ markdown: outputMarkdown
70
+ } = cli.flags
71
+
72
+ if (!cli.input[0]) {
73
+ console.error(`${chalk.bgRed('Input error')}: Please provide an organization slug and repository name \n`)
74
+ cli.showHelp()
75
+ return
76
+ }
77
+
78
+ const [orgSlug = '', repositoryName = ''] = cli.input
79
+
80
+ return {
81
+ outputJson,
82
+ outputMarkdown,
83
+ orgSlug,
84
+ repositoryName
85
+ }
86
+ }
87
+
88
+ /**
89
+ * @typedef RepositoryData
90
+ * @property {import('@socketsecurity/sdk').SocketSdkReturnType<'getOrgRepo'>["data"]} data
91
+ */
92
+
93
+ /**
94
+ * @param {string} orgSlug
95
+ * @param {string} repoName
96
+ * @param {import('ora').Ora} spinner
97
+ * @returns {Promise<void|RepositoryData>}
98
+ */
99
+ async function viewRepository (orgSlug, repoName, spinner) {
100
+ const socketSdk = await setupSdk(getDefaultKey())
101
+ const result = await handleApiCall(socketSdk.getOrgRepo(orgSlug, repoName), 'fetching repository')
102
+
103
+ if (!result.success) {
104
+ return handleUnsuccessfulApiResponse('getOrgRepo', result, spinner)
105
+ }
106
+
107
+ spinner.stop()
108
+
109
+ const options = {
110
+ columns: [
111
+ { field: 'id', name: chalk.magenta('ID') },
112
+ { field: 'name', name: chalk.magenta('Name') },
113
+ { field: 'visibility', name: chalk.magenta('Visibility') },
114
+ { field: 'default_branch', name: chalk.magenta('Default branch') },
115
+ { field: 'homepage', name: chalk.magenta('Homepage') },
116
+ { field: 'archived', name: chalk.magenta('Archived') },
117
+ { field: 'created_at', name: chalk.magenta('Created at') }
118
+ ]
119
+ }
120
+
121
+ const table = chalkTable(options, [result.data])
122
+
123
+ console.log(table, '\n')
124
+
125
+ return {
126
+ data: result.data
127
+ }
128
+ }
@@ -0,0 +1,245 @@
1
+ /* eslint-disable no-console */
2
+
3
+ import { stdin as inputText, stdout as output } from 'node:process'
4
+ import * as readline from 'node:readline/promises'
5
+
6
+ import chalk from 'chalk'
7
+ import meow from 'meow'
8
+ import open from 'open'
9
+ import ora from 'ora'
10
+ import { ErrorWithCause } from 'pony-cause'
11
+
12
+ import { handleApiCall, handleUnsuccessfulApiResponse } from '../../utils/api-helpers.js'
13
+ import { prepareFlags } from '../../utils/flags.js'
14
+ import { printFlagList } from '../../utils/formatting.js'
15
+ import { createDebugLogger } from '../../utils/misc.js'
16
+ import { getPackageFilesFullScans } from '../../utils/path-resolve.js'
17
+ import { getDefaultKey, setupSdk } from '../../utils/sdk.js'
18
+
19
+ /** @type {import('../../utils/meow-with-subcommands.js').CliSubcommand} */
20
+ export const create = {
21
+ description: 'Create a scan',
22
+ async run (argv, importMeta, { parentName }) {
23
+ const name = parentName + ' create'
24
+
25
+ const input = await setupCommand(name, create.description, argv, importMeta)
26
+ if (input) {
27
+ const spinnerText = 'Creating a scan... \n'
28
+ const spinner = ora(spinnerText).start()
29
+
30
+ await createFullScan(input, spinner)
31
+ }
32
+ }
33
+ }
34
+
35
+ const createFullScanFlags = prepareFlags({
36
+ repo: {
37
+ type: 'string',
38
+ shortFlag: 'r',
39
+ default: '',
40
+ description: 'Repository name',
41
+ },
42
+ branch: {
43
+ type: 'string',
44
+ shortFlag: 'b',
45
+ default: '',
46
+ description: 'Branch name',
47
+ },
48
+ commitMessage: {
49
+ type: 'string',
50
+ shortFlag: 'm',
51
+ default: '',
52
+ description: 'Commit message',
53
+ },
54
+ commitHash: {
55
+ type: 'string',
56
+ shortFlag: 'ch',
57
+ default: '',
58
+ description: 'Commit hash',
59
+ },
60
+ pullRequest: {
61
+ type: 'number',
62
+ shortFlag: 'pr',
63
+ description: 'Commit hash',
64
+ },
65
+ committers: {
66
+ type: 'string',
67
+ shortFlag: 'c',
68
+ default: '',
69
+ description: 'Committers',
70
+ },
71
+ defaultBranch: {
72
+ type: 'boolean',
73
+ shortFlag: 'db',
74
+ default: false,
75
+ description: 'Make default branch',
76
+ },
77
+ pendingHead: {
78
+ type: 'boolean',
79
+ shortFlag: 'ph',
80
+ default: false,
81
+ description: 'Set as pending head',
82
+ },
83
+ tmp: {
84
+ type: 'boolean',
85
+ shortFlag: 't',
86
+ default: false,
87
+ description: 'Set the visibility (true/false) of the scan in your dashboard',
88
+ }
89
+ })
90
+
91
+ // Internal functions
92
+
93
+ /**
94
+ * @typedef CommandContext
95
+ * @property {string} orgSlug
96
+ * @property {string} repoName
97
+ * @property {string} branchName
98
+ * @property {string} committers
99
+ * @property {string} commitMessage
100
+ * @property {string} commitHash
101
+ * @property {number | undefined} pullRequest
102
+ * @property {boolean} defaultBranch
103
+ * @property {boolean} pendingHead
104
+ * @property {boolean} tmp
105
+ * @property {string[]} packagePaths
106
+ */
107
+
108
+ /**
109
+ * @param {string} name
110
+ * @param {string} description
111
+ * @param {readonly string[]} argv
112
+ * @param {ImportMeta} importMeta
113
+ * @returns {Promise<void|CommandContext>}
114
+ */
115
+ async function setupCommand (name, description, argv, importMeta) {
116
+ const flags = {
117
+ ...createFullScanFlags
118
+ }
119
+
120
+ const cli = meow(`
121
+ Usage
122
+ $ ${name} [...options]
123
+
124
+ Options
125
+ ${printFlagList(flags, 6)}
126
+
127
+ Examples
128
+ $ ${name} --org=FakeOrg --repo=test-repo --branch=main ./package.json
129
+ `, {
130
+ argv,
131
+ description,
132
+ importMeta,
133
+ flags
134
+ })
135
+
136
+ const {
137
+ repo: repoName,
138
+ branch: branchName,
139
+ commitMessage,
140
+ defaultBranch,
141
+ pendingHead,
142
+ tmp,
143
+ committers,
144
+ commitHash,
145
+ pullRequest
146
+ } = cli.flags
147
+
148
+ if (!cli.input[0]) {
149
+ cli.showHelp()
150
+ return
151
+ }
152
+
153
+ const [orgSlug = ''] = cli.input
154
+
155
+ const cwd = process.cwd()
156
+ const socketSdk = await setupSdk()
157
+ const supportedFiles = await socketSdk.getReportSupportedFiles()
158
+ .then(res => {
159
+ if (!res.success) handleUnsuccessfulApiResponse('getReportSupportedFiles', res, ora())
160
+ return res.data
161
+ }).catch(
162
+ /** @type {(cause: Error) => never} */
163
+ (cause) => {
164
+ throw new ErrorWithCause('Failed getting supported files for report', { cause })
165
+ })
166
+ const debugLog = createDebugLogger(false)
167
+ const packagePaths = await getPackageFilesFullScans(cwd, cli.input, supportedFiles, debugLog)
168
+
169
+ if (!repoName || !branchName || !packagePaths.length) {
170
+ console.error(`${chalk.bgRed('Input error')}: Please provide the required fields: \n
171
+ - Repository name using --repo, \n
172
+ - Branch name using --branch \n
173
+ - At least one file path (e.g. ./package.json) .\n`)
174
+ cli.showHelp()
175
+ return
176
+ }
177
+
178
+ return {
179
+ orgSlug,
180
+ repoName,
181
+ branchName,
182
+ commitMessage,
183
+ defaultBranch,
184
+ pendingHead,
185
+ tmp,
186
+ packagePaths,
187
+ commitHash,
188
+ committers,
189
+ pullRequest
190
+ }
191
+ }
192
+
193
+ /**
194
+ * @typedef FullScanData
195
+ * @property {import('@socketsecurity/sdk').SocketSdkReturnType<'CreateOrgFullScan'>["data"]} data
196
+ */
197
+
198
+ /**
199
+ * @param {CommandContext} input
200
+ * @param {import('ora').Ora} spinner
201
+ * @returns {Promise<void|FullScanData>}
202
+ */
203
+ async function createFullScan (input, spinner) {
204
+ const socketSdk = await setupSdk(getDefaultKey())
205
+ const {
206
+ orgSlug,
207
+ repoName,
208
+ branchName,
209
+ commitMessage,
210
+ defaultBranch,
211
+ pendingHead,
212
+ tmp,
213
+ packagePaths
214
+ } = input
215
+
216
+ const result = await handleApiCall(socketSdk.createOrgFullScan(orgSlug, {
217
+ repo: repoName,
218
+ branch: branchName,
219
+ commit_message: commitMessage,
220
+ make_default_branch: defaultBranch,
221
+ set_as_pending_head: pendingHead,
222
+ tmp
223
+ }, packagePaths), 'Creating scan')
224
+
225
+ if (!result.success) {
226
+ return handleUnsuccessfulApiResponse('CreateOrgFullScan', result, spinner)
227
+ }
228
+ spinner.stop()
229
+
230
+ console.log('\n✅ Scan created successfully \n')
231
+ const link = chalk.hex('#00FFFF').underline(`${result.data.html_report_url}`)
232
+ console.log(`Available at: ${link} \n`)
233
+
234
+ const rl = readline.createInterface({ input: inputText, output })
235
+
236
+ const answer = await rl.question('Would you like to open it in your browser? (y/n) ')
237
+
238
+ answer.toLowerCase() === 'y' && open(`${result.data.html_report_url}`)
239
+
240
+ rl.close()
241
+
242
+ return {
243
+ data: result.data
244
+ }
245
+ }
@@ -0,0 +1,112 @@
1
+ /* eslint-disable no-console */
2
+
3
+ import chalk from 'chalk'
4
+ import meow from 'meow'
5
+ import ora from 'ora'
6
+
7
+ import { outputFlags } from '../../flags/index.js'
8
+ import { handleApiCall, handleUnsuccessfulApiResponse } from '../../utils/api-helpers.js'
9
+ import { printFlagList } from '../../utils/formatting.js'
10
+ import { getDefaultKey, setupSdk } from '../../utils/sdk.js'
11
+
12
+ /** @type {import('../../utils/meow-with-subcommands.js').CliSubcommand} */
13
+ export const del = {
14
+ description: 'Delete a scan',
15
+ async run (argv, importMeta, { parentName }) {
16
+ const name = parentName + ' del'
17
+
18
+ const input = setupCommand(name, del.description, argv, importMeta)
19
+ if (input) {
20
+ const spinnerText = 'Deleting scan...'
21
+ const spinner = ora(spinnerText).start()
22
+ await deleteOrgFullScan(input.orgSlug, input.fullScanId, spinner)
23
+ }
24
+ }
25
+ }
26
+
27
+ // Internal functions
28
+
29
+ /**
30
+ * @typedef CommandContext
31
+ * @property {boolean} outputJson
32
+ * @property {boolean} outputMarkdown
33
+ * @property {string} orgSlug
34
+ * @property {string} fullScanId
35
+ */
36
+
37
+ /**
38
+ * @param {string} name
39
+ * @param {string} description
40
+ * @param {readonly string[]} argv
41
+ * @param {ImportMeta} importMeta
42
+ * @returns {void|CommandContext}
43
+ */
44
+ function setupCommand (name, description, argv, importMeta) {
45
+ const flags = {
46
+ ...outputFlags
47
+ }
48
+
49
+ const cli = meow(`
50
+ Usage
51
+ $ ${name} <org slug> <scan ID>
52
+
53
+ Options
54
+ ${printFlagList(flags, 6)}
55
+
56
+ Examples
57
+ $ ${name} FakeOrg 000aaaa1-0000-0a0a-00a0-00a0000000a0
58
+ `, {
59
+ argv,
60
+ description,
61
+ importMeta,
62
+ flags
63
+ })
64
+
65
+ const {
66
+ json: outputJson,
67
+ markdown: outputMarkdown,
68
+ } = cli.flags
69
+
70
+ if (cli.input.length < 2) {
71
+ console.error(`${chalk.bgRed('Input error')}: Please specify an organization slug and a scan ID.\n`)
72
+ cli.showHelp()
73
+ return
74
+ }
75
+
76
+ const [orgSlug = '', fullScanId = ''] = cli.input
77
+
78
+ return {
79
+ outputJson,
80
+ outputMarkdown,
81
+ orgSlug,
82
+ fullScanId
83
+ }
84
+ }
85
+
86
+ /**
87
+ * @typedef FullScanData
88
+ * @property {import('@socketsecurity/sdk').SocketSdkReturnType<'deleteOrgFullScan'>["data"]} data
89
+ */
90
+
91
+ /**
92
+ * @param {string} orgSlug
93
+ * @param {string} fullScanId
94
+ * @param {import('ora').Ora} spinner
95
+ * @returns {Promise<void|FullScanData>}
96
+ */
97
+ async function deleteOrgFullScan (orgSlug, fullScanId, spinner) {
98
+ const socketSdk = await setupSdk(getDefaultKey())
99
+ const result = await handleApiCall(socketSdk.deleteOrgFullScan(orgSlug, fullScanId), 'Deleting scan')
100
+
101
+ if (!result.success) {
102
+ return handleUnsuccessfulApiResponse('deleteOrgFullScan', result, spinner)
103
+ }
104
+
105
+ console.log('\n ✅ Scan deleted successfully. \n')
106
+
107
+ spinner.stop()
108
+
109
+ return {
110
+ data: result.data
111
+ }
112
+ }
@@ -0,0 +1,30 @@
1
+ import { create } from './create.js'
2
+ import { del } from './delete.js'
3
+ import { list } from './list.js'
4
+ import { metadata } from './metadata.js'
5
+ import { stream } from './stream.js'
6
+ import { meowWithSubcommands } from '../../utils/meow-with-subcommands.js'
7
+
8
+ const description = 'Scans related commands'
9
+
10
+ /** @type {import('../../utils/meow-with-subcommands.js').CliSubcommand} */
11
+ export const scan = {
12
+ description,
13
+ run: async (argv, importMeta, { parentName }) => {
14
+ await meowWithSubcommands(
15
+ {
16
+ create,
17
+ stream,
18
+ list,
19
+ del,
20
+ metadata
21
+ },
22
+ {
23
+ argv,
24
+ description,
25
+ importMeta,
26
+ name: parentName + ' scan',
27
+ }
28
+ )
29
+ }
30
+ }
@@ -0,0 +1,192 @@
1
+ /* eslint-disable no-console */
2
+
3
+ import chalk from 'chalk'
4
+ // @ts-ignore
5
+ import chalkTable from 'chalk-table'
6
+ import meow from 'meow'
7
+ import ora from 'ora'
8
+
9
+ import { outputFlags } from '../../flags/index.js'
10
+ import { handleApiCall, handleUnsuccessfulApiResponse } from '../../utils/api-helpers.js'
11
+ import { prepareFlags } from '../../utils/flags.js'
12
+ import { printFlagList } from '../../utils/formatting.js'
13
+ import { getDefaultKey, setupSdk } from '../../utils/sdk.js'
14
+
15
+ /** @type {import('../../utils/meow-with-subcommands.js').CliSubcommand} */
16
+ export const list = {
17
+ description: 'List scans for an organization',
18
+ async run (argv, importMeta, { parentName }) {
19
+ const name = parentName + ' list'
20
+
21
+ const input = setupCommand(name, list.description, argv, importMeta)
22
+ if (input) {
23
+ const spinnerText = 'Listing scans... \n'
24
+ const spinner = ora(spinnerText).start()
25
+ await listOrgFullScan(input.orgSlug, input, spinner)
26
+ }
27
+ }
28
+ }
29
+
30
+ const listFullScanFlags = prepareFlags({
31
+ sort: {
32
+ type: 'string',
33
+ shortFlag: 's',
34
+ default: 'created_at',
35
+ description: 'Sorting option (`name` or `created_at`) - default is `created_at`',
36
+ },
37
+ direction: {
38
+ type: 'string',
39
+ shortFlag: 'd',
40
+ default: 'desc',
41
+ description: 'Direction option (`desc` or `asc`) - Default is `desc`',
42
+ },
43
+ perPage: {
44
+ type: 'number',
45
+ shortFlag: 'pp',
46
+ default: 30,
47
+ description: 'Results per page - Default is 30',
48
+ },
49
+ page: {
50
+ type: 'number',
51
+ shortFlag: 'p',
52
+ default: 1,
53
+ description: 'Page number - Default is 1',
54
+ },
55
+ fromTime: {
56
+ type: 'string',
57
+ shortFlag: 'f',
58
+ default: '',
59
+ description: 'From time - as a unix timestamp',
60
+ },
61
+ untilTime: {
62
+ type: 'string',
63
+ shortFlag: 'u',
64
+ default: '',
65
+ description: 'Until time - as a unix timestamp',
66
+ }
67
+ })
68
+
69
+ // Internal functions
70
+
71
+ /**
72
+ * @typedef CommandContext
73
+ * @property {boolean} outputJson
74
+ * @property {boolean} outputMarkdown
75
+ * @property {string} orgSlug
76
+ * @property {string} sort
77
+ * @property {string} direction
78
+ * @property {number} per_page
79
+ * @property {number} page
80
+ * @property {string} from_time
81
+ * @property {string} until_time
82
+ */
83
+
84
+ /**
85
+ * @param {string} name
86
+ * @param {string} description
87
+ * @param {readonly string[]} argv
88
+ * @param {ImportMeta} importMeta
89
+ * @returns {void|CommandContext}
90
+ */
91
+ function setupCommand (name, description, argv, importMeta) {
92
+ const flags = {
93
+ ...outputFlags,
94
+ ...listFullScanFlags
95
+ }
96
+
97
+ const cli = meow(`
98
+ Usage
99
+ $ ${name} <org slug>
100
+
101
+ Options
102
+ ${printFlagList(flags, 6)}
103
+
104
+ Examples
105
+ $ ${name} FakeOrg
106
+ `, {
107
+ argv,
108
+ description,
109
+ importMeta,
110
+ flags
111
+ })
112
+
113
+ const {
114
+ json: outputJson,
115
+ markdown: outputMarkdown,
116
+ sort,
117
+ direction,
118
+ perPage,
119
+ page,
120
+ fromTime,
121
+ untilTime
122
+ } = cli.flags
123
+
124
+ if (!cli.input[0]) {
125
+ console.error(`${chalk.bgRed('Input error')}: Please specify an organization slug.\n`)
126
+ cli.showHelp()
127
+ return
128
+ }
129
+
130
+ const [orgSlug = ''] = cli.input
131
+
132
+ return {
133
+ outputJson,
134
+ outputMarkdown,
135
+ orgSlug,
136
+ sort,
137
+ direction,
138
+ per_page: perPage,
139
+ page,
140
+ from_time: fromTime,
141
+ until_time: untilTime
142
+ }
143
+ }
144
+
145
+ /**
146
+ * @typedef FullScansData
147
+ * @property {import('@socketsecurity/sdk').SocketSdkReturnType<'getOrgFullScanList'>["data"]} data
148
+ */
149
+
150
+ /**
151
+ * @param {string} orgSlug
152
+ * @param {CommandContext} input
153
+ * @param {import('ora').Ora} spinner
154
+ * @returns {Promise<void|FullScansData>}
155
+ */
156
+ async function listOrgFullScan (orgSlug, input, spinner) {
157
+ const socketSdk = await setupSdk(getDefaultKey())
158
+ const result = await handleApiCall(socketSdk.getOrgFullScanList(orgSlug, input), 'Listing scans')
159
+
160
+ if (!result.success) {
161
+ return handleUnsuccessfulApiResponse('getOrgFullScanList', result, spinner)
162
+ }
163
+ spinner.stop()
164
+
165
+ console.log(`\n Listing scans for: ${orgSlug} \n`)
166
+
167
+ const options = {
168
+ columns: [
169
+ { field: 'id', name: chalk.magenta('ID') },
170
+ { field: 'report_url', name: chalk.magenta('Scan URL') },
171
+ { field: 'branch', name: chalk.magenta('Branch') },
172
+ { field: 'created_at', name: chalk.magenta('Created at') }
173
+ ]
174
+ }
175
+
176
+ const formattedResults = result.data.results.map(d => {
177
+ return {
178
+ id: d.id,
179
+ report_url: chalk.underline(`${d.html_report_url}`),
180
+ created_at: d.created_at ? new Date(d.created_at).toLocaleDateString('en-us', { year: 'numeric', month: 'numeric', day: 'numeric' }) : '',
181
+ branch: d.branch
182
+ }
183
+ })
184
+
185
+ const table = chalkTable(options, formattedResults)
186
+
187
+ console.log(table, '\n')
188
+
189
+ return {
190
+ data: result.data
191
+ }
192
+ }