From 259b574bf9e319e142c7f3e643bace1196b4ab6c Mon Sep 17 00:00:00 2001 From: Peter van der Zee Date: Fri, 4 Apr 2025 17:01:23 +0200 Subject: [PATCH 1/2] Add --report to socket scan create, to immediately generate a report --- src/commands/scan/cmd-scan-create.ts | 75 ++++++++++++++++----- src/commands/scan/handle-create-new-scan.ts | 25 ++++++- src/commands/scan/output-create-new-scan.ts | 36 +++++++++- 3 files changed, 116 insertions(+), 20 deletions(-) diff --git a/src/commands/scan/cmd-scan-create.ts b/src/commands/scan/cmd-scan-create.ts index d915241a1..e7e568043 100644 --- a/src/commands/scan/cmd-scan-create.ts +++ b/src/commands/scan/cmd-scan-create.ts @@ -6,7 +6,7 @@ import { suggestRepoSlug } from './suggest-repo-slug' import { suggestBranchSlug } from './suggest_branch_slug' import { suggestTarget } from './suggest_target' import constants from '../../constants' -import { commonFlags } from '../../flags' +import { commonFlags, outputFlags } from '../../flags' import { getConfigValue } from '../../utils/config' import { handleBadInput } from '../../utils/handle-bad-input' import { meowOrExit } from '../../utils/meow-with-subcommands' @@ -23,6 +23,7 @@ const config: CliCommandConfig = { hidden: false, flags: { ...commonFlags, + ...outputFlags, repo: { type: 'string', shortFlag: 'r', @@ -51,6 +52,18 @@ const config: CliCommandConfig = { type: 'string', description: 'working directory, defaults to process.cwd()' }, + defaultBranch: { + type: 'boolean', + default: false, + description: + 'Set the default branch of the repository to the branch of this full-scan. Should only need to be done once, for example for the "main" or "master" branch.' + }, + pendingHead: { + type: 'boolean', + default: true, + description: + 'Designate this full-scan as the latest scan of a given branch. This must be set to have it show up in the dashboard.' + }, dryRun: { type: 'boolean', description: @@ -67,23 +80,17 @@ const config: CliCommandConfig = { default: '', description: 'Committers' }, - defaultBranch: { - type: 'boolean', - shortFlag: 'db', - default: false, - description: 'Make default branch' - }, - pendingHead: { + readOnly: { type: 'boolean', - shortFlag: 'ph', default: false, - description: 'Set as pending head' + description: + 'Similar to --dry-run except it can read from remote, stops before it would create an actual report' }, - readOnly: { + report: { type: 'boolean', default: false, description: - 'Similar to --dry-run except it can read from remote, stops before it would create an actual report' + 'Wait for the scan creation to complete, then basically run `socket scan report` on it' }, tmp: { type: 'boolean', @@ -123,6 +130,12 @@ const config: CliCommandConfig = { When a FILE is given only that FILE is targeted. Otherwise any eligible files in the given DIR will be considered. + Note: for a first run you probably want to set --defaultBranch to indicate + the default branch name, like "main" or "master". + + Note: --pendingHead is enabled by default and makes a scan show up in your + dashboard. You can use \`--no-pendingHead\` to have it not show up. + Options ${getFlagListOutput(config.flags, 6)} @@ -149,9 +162,26 @@ async function run( parentName }) - const { cwd: cwdOverride, dryRun } = cli.flags as { + const { + cwd: cwdOverride, + defaultBranch, + dryRun, + json, + markdown, + pendingHead, + readOnly, + report, + tmp + } = cli.flags as { cwd: string dryRun: boolean + report: boolean + json: boolean + markdown: boolean + defaultBranch: boolean + pendingHead: boolean + readOnly: boolean + tmp: boolean } const defaultOrgSlug = getConfigValue('defaultOrg') let orgSlug = defaultOrgSlug || cli.input[0] || '' @@ -220,7 +250,7 @@ async function run( ) logger.error('```') logger.error( - ` socket scan create [other flags...] --repo ${repoName} --branch ${branchName} ${orgSlug} ${targets.join(' ')}` + ` socket scan create [other flags...] --repo ${repoName} --branch ${branchName} ${defaultOrgSlug ? '' : orgSlug} ${targets.join(' ')}` ) logger.error('```\n') } @@ -251,6 +281,13 @@ async function run( pass: 'ok', fail: 'missing' }, + { + nook: true, + test: !json || !markdown, + message: 'The json and markdown flags cannot be both set, pick one', + pass: 'ok', + fail: 'omit one' + }, { nook: true, test: apiToken, @@ -273,12 +310,14 @@ async function run( branchName: branchName as string, commitMessage: (cli.flags['commitMessage'] as string | undefined) ?? '', cwd, - defaultBranch: Boolean(cli.flags['defaultBranch']), + defaultBranch: Boolean(defaultBranch), orgSlug, - pendingHead: Boolean(cli.flags['pendingHead']), - readOnly: Boolean(cli.flags['readOnly']), + outputKind: json ? 'json' : markdown ? 'markdown' : 'text', + pendingHead: Boolean(pendingHead), + readOnly: Boolean(readOnly), repoName: repoName, + report, targets, - tmp: Boolean(cli.flags['tmp']) + tmp: Boolean(tmp) }) } diff --git a/src/commands/scan/handle-create-new-scan.ts b/src/commands/scan/handle-create-new-scan.ts index f0d44883a..a4bcaff5b 100644 --- a/src/commands/scan/handle-create-new-scan.ts +++ b/src/commands/scan/handle-create-new-scan.ts @@ -2,6 +2,7 @@ import { logger } from '@socketsecurity/registry/lib/logger' import { fetchCreateOrgFullScan } from './fetch-create-org-full-scan' import { fetchSupportedScanFileNames } from './fetch-supported-scan-file-names' +import { handleScanReport } from './handle-scan-report' import { outputCreateNewScan } from './output-create-new-scan' import { handleBadInput } from '../../utils/handle-bad-input' import { getPackageFilesForScan } from '../../utils/path-resolve' @@ -12,9 +13,11 @@ export async function handleCreateNewScan({ cwd, defaultBranch, orgSlug, + outputKind, pendingHead, readOnly, repoName, + report, targets, tmp }: { @@ -24,8 +27,10 @@ export async function handleCreateNewScan({ defaultBranch: boolean orgSlug: string pendingHead: boolean + outputKind: 'json' | 'markdown' | 'text' readOnly: boolean repoName: string + report: boolean targets: string[] tmp: boolean }): Promise { @@ -68,5 +73,23 @@ export async function handleCreateNewScan({ return } - await outputCreateNewScan(data) + if (report) { + if (data?.id) { + await handleScanReport({ + filePath: '-', + fold: 'version', + includeLicensePolicy: true, + orgSlug, + outputKind, + reportLevel: 'error', + scanId: data.id, + short: false + }) + } else { + logger.fail('Failure: Server did not respond with a scan ID') + process.exitCode = 1 + } + } else { + await outputCreateNewScan(data, outputKind) + } } diff --git a/src/commands/scan/output-create-new-scan.ts b/src/commands/scan/output-create-new-scan.ts index 6b71c8227..52071d20b 100644 --- a/src/commands/scan/output-create-new-scan.ts +++ b/src/commands/scan/output-create-new-scan.ts @@ -7,8 +7,42 @@ import { confirm } from '@socketsecurity/registry/lib/prompts' import type { SocketSdkReturnType } from '@socketsecurity/sdk' export async function outputCreateNewScan( - data: SocketSdkReturnType<'CreateOrgFullScan'>['data'] + data: SocketSdkReturnType<'CreateOrgFullScan'>['data'], + outputKind: 'json' | 'markdown' | 'text' ) { + if (!data.id) { + logger.fail('Did not receive a scan ID from the API...') + process.exitCode = 1 + } + + if (outputKind === 'json') { + const json = data.id + ? { success: true, data } + : { success: false, message: 'No scan ID received' } + + logger.log(JSON.stringify(json, null, 2)) + logger.log('') + + return + } + + if (outputKind === 'markdown') { + logger.log('# Create New Scan') + logger.log('') + if (data.id) { + logger.log( + `A [new Scan](${data.html_report_url}) was created with ID: ${data.id}` + ) + logger.log('') + } else { + logger.log( + `The server did not return a Scan ID while trying to create a new Scan. This could be an indication something went wrong.` + ) + } + logger.log('') + return + } + const link = colors.underline(colors.cyan(`${data.html_report_url}`)) logger.log(`Available at: ${link}`) From 6fbd023964117088dd91f5e9efa42d05ac7e13ff Mon Sep 17 00:00:00 2001 From: Peter van der Zee Date: Fri, 4 Apr 2025 18:07:45 +0200 Subject: [PATCH 2/2] Make repo/branch flags optional and use server defaults for fallback --- src/commands/scan/cmd-scan-create.test.ts | 15 ++++++-- src/commands/scan/cmd-scan-create.ts | 46 +++-------------------- 2 files changed, 18 insertions(+), 43 deletions(-) diff --git a/src/commands/scan/cmd-scan-create.test.ts b/src/commands/scan/cmd-scan-create.test.ts index 4d947e502..a8af7a3c7 100644 --- a/src/commands/scan/cmd-scan-create.test.ts +++ b/src/commands/scan/cmd-scan-create.test.ts @@ -40,21 +40,30 @@ describe('socket scan create', async () => { When a FILE is given only that FILE is targeted. Otherwise any eligible files in the given DIR will be considered. + Note: for a first run you probably want to set --defaultBranch to indicate + the default branch name, like "main" or "master". + + Note: --pendingHead is enabled by default and makes a scan show up in your + dashboard. You can use \`--no-pendingHead\` to have it not show up. + Options --branch Branch name --commitHash Commit hash --commitMessage Commit message --committers Committers --cwd working directory, defaults to process.cwd() - --defaultBranch Make default branch + --defaultBranch Set the default branch of the repository to the branch of this full-scan. Should only need to be done once, for example for the "main" or "master" branch. --dryRun run input validation part of command without any concrete side effects --help Print this help - --pendingHead Set as pending head + --json Output result as json + --markdown Output result as markdown + --pendingHead Designate this full-scan as the latest scan of a given branch. This must be set to have it show up in the dashboard. --pullRequest Commit hash --readOnly Similar to --dry-run except it can read from remote, stops before it would create an actual report --repo Repository name + --report Wait for the scan creation to complete, then basically run \`socket scan report\` on it --tmp Set the visibility (true/false) of the scan in your dashboard - --view Will wait for and return the created report. Use --no-view to disable. + --view Will wait for and return the created scan details. Use --no-view to disable. Examples $ socket scan create --repo=test-repo --branch=main FakeOrg ./package.json" diff --git a/src/commands/scan/cmd-scan-create.ts b/src/commands/scan/cmd-scan-create.ts index e7e568043..643ba01e2 100644 --- a/src/commands/scan/cmd-scan-create.ts +++ b/src/commands/scan/cmd-scan-create.ts @@ -2,8 +2,6 @@ import { logger } from '@socketsecurity/registry/lib/logger' import { handleCreateNewScan } from './handle-create-new-scan' import { suggestOrgSlug } from './suggest-org-slug' -import { suggestRepoSlug } from './suggest-repo-slug' -import { suggestBranchSlug } from './suggest_branch_slug' import { suggestTarget } from './suggest_target' import constants from '../../constants' import { commonFlags, outputFlags } from '../../flags' @@ -27,13 +25,13 @@ const config: CliCommandConfig = { repo: { type: 'string', shortFlag: 'r', - default: '', + default: 'socket-default-repository', description: 'Repository name' }, branch: { type: 'string', shortFlag: 'b', - default: '', + default: 'socket-default-branch', description: 'Branch name' }, commitMessage: { @@ -104,7 +102,7 @@ const config: CliCommandConfig = { shortFlag: 'v', default: true, description: - 'Will wait for and return the created report. Use --no-view to disable.' + 'Will wait for and return the created scan details. Use --no-view to disable.' } }, // TODO: your project's "socket.yml" file's "projectIgnorePaths" @@ -191,7 +189,7 @@ async function run( cwdOverride && cwdOverride !== 'process.cwd()' ? String(cwdOverride) : process.cwd() - let { branch: branchName = '', repo: repoName = '' } = cli.flags as { + const { branch: branchName = '', repo: repoName = '' } = cli.flags as { branch: string repo: string } @@ -213,7 +211,6 @@ async function run( // If the current cwd is unknown and is used as a repo slug anyways, we will // first need to register the slug before we can use it. - let repoDefaultBranch = '' // Only do suggestions with an apiToken and when not in dryRun mode if (apiToken && !dryRun) { if (!orgSlug) { @@ -223,34 +220,15 @@ async function run( } updatedInput = true } - - // (Don't bother asking for the rest if we didn't get an org slug above) - if (orgSlug && !repoName) { - const suggestion = await suggestRepoSlug(orgSlug) - if (suggestion) { - repoDefaultBranch = suggestion.defaultBranch - repoName = suggestion.slug - } - updatedInput = true - } - - // (Don't bother asking for the rest if we didn't get an org/repo above) - if (orgSlug && repoName && !branchName) { - const suggestion = await suggestBranchSlug(repoDefaultBranch) - if (suggestion) { - branchName = suggestion - } - updatedInput = true - } } - if (updatedInput && repoName && branchName && orgSlug && targets?.length) { + if (updatedInput && orgSlug && targets?.length) { logger.error( 'Note: You can invoke this command next time to skip the interactive questions:' ) logger.error('```') logger.error( - ` socket scan create [other flags...] --repo ${repoName} --branch ${branchName} ${defaultOrgSlug ? '' : orgSlug} ${targets.join(' ')}` + ` socket scan create [other flags...] ${defaultOrgSlug ? '' : orgSlug} ${targets.join(' ')}` ) logger.error('```\n') } @@ -263,18 +241,6 @@ async function run( pass: 'ok', fail: 'missing' }, - { - test: repoName, - message: 'Repository name using --repo', - pass: 'ok', - fail: typeof repoName !== 'string' ? 'missing' : 'invalid' - }, - { - test: branchName, - message: 'Repository name using --branch', - pass: 'ok', - fail: typeof repoName !== 'string' ? 'missing' : 'invalid' - }, { test: targets.length, message: 'At least one TARGET (e.g. `.` or `./package.json`)',