Skip to content

Commit

Permalink
Merge pull request #118 from SocketDev/cg/addFullScansFeature
Browse files Browse the repository at this point in the history
Add full scans feature
  • Loading branch information
charliegerard authored Jun 18, 2024
2 parents 566e29b + bda2506 commit 80ca958
Show file tree
Hide file tree
Showing 9 changed files with 840 additions and 1 deletion.
1 change: 1 addition & 0 deletions lib/commands/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export * from './raw-npm/index.js'
export * from './raw-npx/index.js'
export * from './report/index.js'
export * from './wrapper/index.js'
export * from './scan/index.js'
245 changes: 245 additions & 0 deletions lib/commands/scan/create.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
/* eslint-disable no-console */

import { stdin as inputText, stdout as output } from 'node:process'
import * as readline from 'node:readline/promises'

import chalk from 'chalk'
import meow from 'meow'
import open from 'open'
import ora from 'ora'
import { ErrorWithCause } from 'pony-cause'

import { handleApiCall, handleUnsuccessfulApiResponse } from '../../utils/api-helpers.js'
import { prepareFlags } from '../../utils/flags.js'
import { printFlagList } from '../../utils/formatting.js'
import { createDebugLogger } from '../../utils/misc.js'
import { getPackageFilesFullScans } from '../../utils/path-resolve.js'
import { getDefaultKey, setupSdk } from '../../utils/sdk.js'

/** @type {import('../../utils/meow-with-subcommands.js').CliSubcommand} */
export const create = {
description: 'Create a scan',
async run (argv, importMeta, { parentName }) {
const name = parentName + ' create'

const input = await setupCommand(name, create.description, argv, importMeta)
if (input) {
const spinnerText = 'Creating a scan... \n'
const spinner = ora(spinnerText).start()

await createFullScan(input, spinner)
}
}
}

const createFullScanFlags = prepareFlags({
repo: {
type: 'string',
shortFlag: 'r',
default: '',
description: 'Repository name',
},
branch: {
type: 'string',
shortFlag: 'b',
default: '',
description: 'Branch name',
},
commitMessage: {
type: 'string',
shortFlag: 'm',
default: '',
description: 'Commit message',
},
commitHash: {
type: 'string',
shortFlag: 'ch',
default: '',
description: 'Commit hash',
},
pullRequest: {
type: 'number',
shortFlag: 'pr',
description: 'Commit hash',
},
committers: {
type: 'string',
shortFlag: 'c',
default: '',
description: 'Committers',
},
defaultBranch: {
type: 'boolean',
shortFlag: 'db',
default: false,
description: 'Make default branch',
},
pendingHead: {
type: 'boolean',
shortFlag: 'ph',
default: false,
description: 'Set as pending head',
},
tmp: {
type: 'boolean',
shortFlag: 't',
default: false,
description: 'Set the visibility (true/false) of the scan in your dashboard',
}
})

// Internal functions

/**
* @typedef CommandContext
* @property {string} orgSlug
* @property {string} repoName
* @property {string} branchName
* @property {string} committers
* @property {string} commitMessage
* @property {string} commitHash
* @property {number | undefined} pullRequest
* @property {boolean} defaultBranch
* @property {boolean} pendingHead
* @property {boolean} tmp
* @property {string[]} packagePaths
*/

/**
* @param {string} name
* @param {string} description
* @param {readonly string[]} argv
* @param {ImportMeta} importMeta
* @returns {Promise<void|CommandContext>}
*/
async function setupCommand (name, description, argv, importMeta) {
const flags = {
...createFullScanFlags
}

const cli = meow(`
Usage
$ ${name} [...options]
Options
${printFlagList(flags, 6)}
Examples
$ ${name} --org=FakeOrg --repo=test-repo --branch=main ./package.json
`, {
argv,
description,
importMeta,
flags
})

const {
repo: repoName,
branch: branchName,
commitMessage,
defaultBranch,
pendingHead,
tmp,
committers,
commitHash,
pullRequest
} = cli.flags

if (!cli.input[0]) {
cli.showHelp()
return
}

const [orgSlug = ''] = cli.input

const cwd = process.cwd()
const socketSdk = await setupSdk()
const supportedFiles = await socketSdk.getReportSupportedFiles()
.then(res => {
if (!res.success) handleUnsuccessfulApiResponse('getReportSupportedFiles', res, ora())
return res.data
}).catch(
/** @type {(cause: Error) => never} */
(cause) => {
throw new ErrorWithCause('Failed getting supported files for report', { cause })
})
const debugLog = createDebugLogger(false)
const packagePaths = await getPackageFilesFullScans(cwd, cli.input, supportedFiles, debugLog)

if (!repoName || !branchName || !packagePaths.length) {
console.error(`${chalk.bgRed('Input error')}: Please provide the required fields: \n
- Repository name using --repo, \n
- Branch name using --branch \n
- At least one file path (e.g. ./package.json) .\n`)
cli.showHelp()
return
}

return {
orgSlug,
repoName,
branchName,
commitMessage,
defaultBranch,
pendingHead,
tmp,
packagePaths,
commitHash,
committers,
pullRequest
}
}

/**
* @typedef FullScanData
* @property {import('@socketsecurity/sdk').SocketSdkReturnType<'CreateOrgFullScan'>["data"]} data
*/

/**
* @param {CommandContext} input
* @param {import('ora').Ora} spinner
* @returns {Promise<void|FullScanData>}
*/
async function createFullScan (input, spinner) {
const socketSdk = await setupSdk(getDefaultKey())
const {
orgSlug,
repoName,
branchName,
commitMessage,
defaultBranch,
pendingHead,
tmp,
packagePaths
} = input

const result = await handleApiCall(socketSdk.createOrgFullScan(orgSlug, {
repo: repoName,
branch: branchName,
commit_message: commitMessage,
make_default_branch: defaultBranch,
set_as_pending_head: pendingHead,
tmp
}, packagePaths), 'Creating scan')

if (!result.success) {
return handleUnsuccessfulApiResponse('CreateOrgFullScan', result, spinner)
}
spinner.stop()

console.log('\n✅ Scan created successfully \n')
const link = chalk.hex('#00FFFF').underline(`${result.data.html_report_url}`)
console.log(`Available at: ${link} \n`)

const rl = readline.createInterface({ input: inputText, output })

const answer = await rl.question('Would you like to open it in your browser? (y/n) ')

answer.toLowerCase() === 'y' && open(`${result.data.html_report_url}`)

rl.close()

return {
data: result.data
}
}
112 changes: 112 additions & 0 deletions lib/commands/scan/delete.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/* eslint-disable no-console */

import chalk from 'chalk'
import meow from 'meow'
import ora from 'ora'

import { outputFlags } from '../../flags/index.js'
import { handleApiCall, handleUnsuccessfulApiResponse } from '../../utils/api-helpers.js'
import { printFlagList } from '../../utils/formatting.js'
import { getDefaultKey, setupSdk } from '../../utils/sdk.js'

/** @type {import('../../utils/meow-with-subcommands.js').CliSubcommand} */
export const del = {
description: 'Delete a scan',
async run (argv, importMeta, { parentName }) {
const name = parentName + ' del'

const input = setupCommand(name, del.description, argv, importMeta)
if (input) {
const spinnerText = 'Deleting scan...'
const spinner = ora(spinnerText).start()
await deleteOrgFullScan(input.orgSlug, input.fullScanId, spinner)
}
}
}

// Internal functions

/**
* @typedef CommandContext
* @property {boolean} outputJson
* @property {boolean} outputMarkdown
* @property {string} orgSlug
* @property {string} fullScanId
*/

/**
* @param {string} name
* @param {string} description
* @param {readonly string[]} argv
* @param {ImportMeta} importMeta
* @returns {void|CommandContext}
*/
function setupCommand (name, description, argv, importMeta) {
const flags = {
...outputFlags
}

const cli = meow(`
Usage
$ ${name} <org slug> <scan ID>
Options
${printFlagList(flags, 6)}
Examples
$ ${name} FakeOrg 000aaaa1-0000-0a0a-00a0-00a0000000a0
`, {
argv,
description,
importMeta,
flags
})

const {
json: outputJson,
markdown: outputMarkdown,
} = cli.flags

if (cli.input.length < 2) {
console.error(`${chalk.bgRed('Input error')}: Please specify an organization slug and a scan ID.\n`)
cli.showHelp()
return
}

const [orgSlug = '', fullScanId = ''] = cli.input

return {
outputJson,
outputMarkdown,
orgSlug,
fullScanId
}
}

/**
* @typedef FullScanData
* @property {import('@socketsecurity/sdk').SocketSdkReturnType<'deleteOrgFullScan'>["data"]} data
*/

/**
* @param {string} orgSlug
* @param {string} fullScanId
* @param {import('ora').Ora} spinner
* @returns {Promise<void|FullScanData>}
*/
async function deleteOrgFullScan (orgSlug, fullScanId, spinner) {
const socketSdk = await setupSdk(getDefaultKey())
const result = await handleApiCall(socketSdk.deleteOrgFullScan(orgSlug, fullScanId), 'Deleting scan')

if (!result.success) {
return handleUnsuccessfulApiResponse('deleteOrgFullScan', result, spinner)
}

console.log('\n ✅ Scan deleted successfully. \n')

spinner.stop()

return {
data: result.data
}
}
30 changes: 30 additions & 0 deletions lib/commands/scan/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { create } from './create.js'
import { del } from './delete.js'
import { list } from './list.js'
import { metadata } from './metadata.js'
import { stream } from './stream.js'
import { meowWithSubcommands } from '../../utils/meow-with-subcommands.js'

const description = 'Scans related commands'

/** @type {import('../../utils/meow-with-subcommands.js').CliSubcommand} */
export const scan = {
description,
run: async (argv, importMeta, { parentName }) => {
await meowWithSubcommands(
{
create,
stream,
list,
del,
metadata
},
{
argv,
description,
importMeta,
name: parentName + ' scan',
}
)
}
}
Loading

0 comments on commit 80ca958

Please sign in to comment.