Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { logger } from '@socketsecurity/registry/lib/logger'
import { cmdAnalytics } from './commands/analytics/cmd-analytics'
import { cmdAuditLog } from './commands/audit-log/cmd-audit-log'
import { cmdCdxgen } from './commands/cdxgen/cmd-cdxgen'
import { cmdCI } from './commands/ci/cmd-ci'
import { cmdConfig } from './commands/config/cmd-config'
import { cmdScanCreate } from './commands/dependencies/cmd-dependencies'
import { cmdDiffScan } from './commands/diff-scan/cmd-diff-scan'
Expand Down Expand Up @@ -53,6 +54,7 @@ void (async () => {
await meowWithSubcommands(
{
cdxgen: cmdCdxgen,
ci: cmdCI,
config: cmdConfig,
fix: cmdFix,
info: cmdInfo,
Expand All @@ -78,12 +80,7 @@ void (async () => {
manifest: cmdManifest
},
{
aliases: {
ci: {
description: 'Alias for "report create --view --strict"',
argv: ['report', 'create', '--view', '--strict']
}
},
aliases: {},
argv: process.argv.slice(2),
name: SOCKET_CLI_BIN_NAME,
importMeta: { url: `${pathToFileURL(__filename)}` } as ImportMeta
Expand Down
59 changes: 59 additions & 0 deletions src/commands/ci/cmd-ci.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import path from 'node:path'

import { describe, expect } from 'vitest'

import constants from '../../../dist/constants.js'
import { cmdit, invokeNpm } from '../../../test/utils'

const { CLI } = constants

describe('socket oops', async () => {
// Lazily access constants.rootBinPath.
const entryPath = path.join(constants.rootBinPath, `${CLI}.js`)

cmdit(
['oops', '--help', '--config', '{}'],
'should support --help',
async cmd => {
const { code, stderr, stdout } = await invokeNpm(entryPath, cmd)
expect(stdout).toMatchInlineSnapshot(
`
"Trigger an intentional error (for development)

Usage
$ socket oops oops

Don't run me."
`
)
expect(`\n ${stderr}`).toMatchInlineSnapshot(`
"
_____ _ _ /---------------
| __|___ ___| |_ ___| |_ | Socket.dev CLI ver <redacted>
|__ | . | _| '_| -_| _| | Node: <redacted>, API token set: <redacted>
|_____|___|___|_,_|___|_|.dev | Command: \`socket oops\`, cwd: <redacted>"
`)

expect(code, 'help should exit with code 2').toBe(2)
expect(stderr, 'banner includes base command').toContain('`socket oops`')
}
)

cmdit(
['oops', '--dry-run', '--config', '{"apiToken":"anything"}'],
'should require args with just dry-run',
async cmd => {
const { code, stderr, stdout } = await invokeNpm(entryPath, cmd)
expect(stdout).toMatchInlineSnapshot(`"[DryRun]: Bailing now"`)
expect(`\n ${stderr}`).toMatchInlineSnapshot(`
"
_____ _ _ /---------------
| __|___ ___| |_ ___| |_ | Socket.dev CLI ver <redacted>
|__ | . | _| '_| -_| _| | Node: <redacted>, API token set: <redacted>
|_____|___|___|_,_|___|_|.dev | Command: \`socket oops\`, cwd: <redacted>"
`)

expect(code, 'dry-run should exit with code 0 if input ok').toBe(0)
}
)
})
56 changes: 56 additions & 0 deletions src/commands/ci/cmd-ci.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { logger } from '@socketsecurity/registry/lib/logger'

import { handleCI } from './handle-ci'
import constants from '../../constants'
import { commonFlags } from '../../flags'
import { meowOrExit } from '../../utils/meow-with-subcommands'

import type { CliCommandConfig } from '../../utils/meow-with-subcommands'

const { DRY_RUN_BAIL_TEXT } = constants

const config: CliCommandConfig = {
commandName: 'ci',
description:
'Create a new scan and report whether it passes your security policy',
hidden: true,
flags: {
...commonFlags
},
help: (parentName, _config) => `
Usage
$ ${parentName}

This command is intended to use in CI runs to allow automated systems to
accept or reject a current build. When the scan does not pass your security
policy, the exit code will be non-zero.

It will use the default org for the set API token.
`
}

export const cmdCI = {
description: config.description,
hidden: config.hidden,
run
}

async function run(
argv: string[] | readonly string[],
importMeta: ImportMeta,
{ parentName }: { parentName: string }
): Promise<void> {
const cli = meowOrExit({
argv,
config,
importMeta,
parentName
})

if (cli.flags['dryRun']) {
logger.log(DRY_RUN_BAIL_TEXT)
return
}

await handleCI()
}
55 changes: 55 additions & 0 deletions src/commands/ci/fetch-default-org-slug.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { logger } from '@socketsecurity/registry/lib/logger'

import { handleApiCall } from '../../utils/api'
import { getConfigValue } from '../../utils/config'
import { setupSdk } from '../../utils/sdk'

// Use the config defaultOrg when set, otherwise discover from remote
export async function getDefaultOrgSlug(): Promise<string | void> {
let defaultOrg = getConfigValue('defaultOrg')
if (defaultOrg) {
logger.info(`Using default org: ${defaultOrg}`)
} else {
const sockSdk = await setupSdk()
const result = await handleApiCall(
sockSdk.getOrganizations(),
'looking up organizations'
)
// Ignore a failed request here. It was not the primary goal of
// running this command and reporting it only leads to end-user confusion.
if (!result.success) {
logger.fail(
'Failed to fetch default organization from API. Unable to continue.'
)
process.exitCode = 1
return
}
const orgs = result.data.organizations
const keys = Object.keys(orgs)

if (!keys[0]) {
logger.fail(
'Could not find default organization for the current API token. Unable to continue.'
)
process.exitCode = 1
return
}

const slug = (keys[0] in orgs && orgs?.[keys[0]]?.name) ?? undefined

if (slug) {
defaultOrg = slug
logger.info(`Resolved org to: ${defaultOrg}`)
}
}

if (!defaultOrg) {
logger.fail(
'Could not find the default organization for the current API token. Unable to continue.'
)
process.exitCode = 1
return
}

return defaultOrg
}
33 changes: 33 additions & 0 deletions src/commands/ci/handle-ci.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { getDefaultOrgSlug } from './fetch-default-org-slug'
import { handleCreateNewScan } from '../scan/handle-create-new-scan'

export async function handleCI(): Promise<void> {
// ci: {
// description: 'Alias for "report create --view --strict"',
// argv: ['report', 'create', '--view', '--strict']
// }
const orgSlug = await getDefaultOrgSlug()
if (!orgSlug) {
return
}

// TODO: does it make sense to discover the commit details from local git?
// TODO: does it makes sense to use custom branch/repo names here? probably socket.yml, right
await handleCreateNewScan({
branchName: 'socket-default-branch',
commitMessage: '',
commitHash: '',
committers: '',
cwd: process.cwd(),
defaultBranch: false,
orgSlug,
outputKind: 'json',
pendingHead: true, // when true, requires branch name set, tmp false
pullRequest: 0,
repoName: 'socket-default-repository',
readOnly: false,
report: true,
targets: ['.'],
tmp: false // don't set when pendingHead is true
})
}
2 changes: 1 addition & 1 deletion src/commands/scan/cmd-scan-create.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ describe('socket scan create', async () => {
--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
--tmp Set the visibility (true/false) of the scan in your dashboard. Can not be used when --pendingHead is set.

Examples
$ socket scan create --repo=test-repo --branch=main FakeOrg ./package.json"
Expand Down
27 changes: 24 additions & 3 deletions src/commands/scan/cmd-scan-create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ const config: CliCommandConfig = {
shortFlag: 't',
default: false,
description:
'Set the visibility (true/false) of the scan in your dashboard'
'Set the visibility (true/false) of the scan in your dashboard. Can not be used when --pendingHead is set.'
}
},
// TODO: your project's "socket.yml" file's "projectIgnorePaths"
Expand Down Expand Up @@ -154,7 +154,7 @@ async function run(
})

const {
branch: branchName = '',
branch: branchName = 'socket-default-branch',
commitHash,
commitMessage,
committers,
Expand All @@ -166,7 +166,7 @@ async function run(
pendingHead,
pullRequest,
readOnly,
repo: repoName = '',
repo: repoName = 'socket-default-repository',
report,
tmp
} = cli.flags as {
Expand Down Expand Up @@ -264,6 +264,27 @@ async function run(
message: 'This command requires an API token for access',
pass: 'ok',
fail: 'missing (try `socket login`)'
},
{
nook: true,
test: !pendingHead || !tmp,
message: 'Can not use --pendingHead and --tmp at the same time',
pass: 'ok',
fail: 'remove at least one flag'
},
{
nook: true,
test: !pendingHead || !!branchName,
message: 'When --pendingHead is set, --branch is mandatory',
pass: 'ok',
fail: 'missing branch name'
},
{
nook: true,
test: !defaultBranch || !!branchName,
message: 'When --defaultBranch is set, --branch is mandatory',
pass: 'ok',
fail: 'missing branch name'
}
)
if (wasBadInput) {
Expand Down
Loading