Skip to content

Commit 80ca958

Browse files
Merge pull request #118 from SocketDev/cg/addFullScansFeature
Add full scans feature
2 parents 566e29b + bda2506 commit 80ca958

File tree

9 files changed

+840
-1
lines changed

9 files changed

+840
-1
lines changed

lib/commands/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ export * from './raw-npm/index.js'
88
export * from './raw-npx/index.js'
99
export * from './report/index.js'
1010
export * from './wrapper/index.js'
11+
export * from './scan/index.js'

lib/commands/scan/create.js

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}

lib/commands/scan/delete.js

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}

lib/commands/scan/index.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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 commit comments

Comments
 (0)