Skip to content

Commit aa55dd7

Browse files
authored
Update CI command to use scan create flow (#429)
1 parent 24d7acc commit aa55dd7

File tree

11 files changed

+324
-82
lines changed

11 files changed

+324
-82
lines changed

src/cli.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { logger } from '@socketsecurity/registry/lib/logger'
1212
import { cmdAnalytics } from './commands/analytics/cmd-analytics'
1313
import { cmdAuditLog } from './commands/audit-log/cmd-audit-log'
1414
import { cmdCdxgen } from './commands/cdxgen/cmd-cdxgen'
15+
import { cmdCI } from './commands/ci/cmd-ci'
1516
import { cmdConfig } from './commands/config/cmd-config'
1617
import { cmdScanCreate } from './commands/dependencies/cmd-dependencies'
1718
import { cmdDiffScan } from './commands/diff-scan/cmd-diff-scan'
@@ -53,6 +54,7 @@ void (async () => {
5354
await meowWithSubcommands(
5455
{
5556
cdxgen: cmdCdxgen,
57+
ci: cmdCI,
5658
config: cmdConfig,
5759
fix: cmdFix,
5860
info: cmdInfo,
@@ -78,12 +80,7 @@ void (async () => {
7880
manifest: cmdManifest
7981
},
8082
{
81-
aliases: {
82-
ci: {
83-
description: 'Alias for "report create --view --strict"',
84-
argv: ['report', 'create', '--view', '--strict']
85-
}
86-
},
83+
aliases: {},
8784
argv: process.argv.slice(2),
8885
name: SOCKET_CLI_BIN_NAME,
8986
importMeta: { url: `${pathToFileURL(__filename)}` } as ImportMeta

src/commands/ci/cmd-ci.test.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import path from 'node:path'
2+
3+
import { describe, expect } from 'vitest'
4+
5+
import constants from '../../../dist/constants.js'
6+
import { cmdit, invokeNpm } from '../../../test/utils'
7+
8+
const { CLI } = constants
9+
10+
describe('socket oops', async () => {
11+
// Lazily access constants.rootBinPath.
12+
const entryPath = path.join(constants.rootBinPath, `${CLI}.js`)
13+
14+
cmdit(
15+
['oops', '--help', '--config', '{}'],
16+
'should support --help',
17+
async cmd => {
18+
const { code, stderr, stdout } = await invokeNpm(entryPath, cmd)
19+
expect(stdout).toMatchInlineSnapshot(
20+
`
21+
"Trigger an intentional error (for development)
22+
23+
Usage
24+
$ socket oops oops
25+
26+
Don't run me."
27+
`
28+
)
29+
expect(`\n ${stderr}`).toMatchInlineSnapshot(`
30+
"
31+
_____ _ _ /---------------
32+
| __|___ ___| |_ ___| |_ | Socket.dev CLI ver <redacted>
33+
|__ | . | _| '_| -_| _| | Node: <redacted>, API token set: <redacted>
34+
|_____|___|___|_,_|___|_|.dev | Command: \`socket oops\`, cwd: <redacted>"
35+
`)
36+
37+
expect(code, 'help should exit with code 2').toBe(2)
38+
expect(stderr, 'banner includes base command').toContain('`socket oops`')
39+
}
40+
)
41+
42+
cmdit(
43+
['oops', '--dry-run', '--config', '{"apiToken":"anything"}'],
44+
'should require args with just dry-run',
45+
async cmd => {
46+
const { code, stderr, stdout } = await invokeNpm(entryPath, cmd)
47+
expect(stdout).toMatchInlineSnapshot(`"[DryRun]: Bailing now"`)
48+
expect(`\n ${stderr}`).toMatchInlineSnapshot(`
49+
"
50+
_____ _ _ /---------------
51+
| __|___ ___| |_ ___| |_ | Socket.dev CLI ver <redacted>
52+
|__ | . | _| '_| -_| _| | Node: <redacted>, API token set: <redacted>
53+
|_____|___|___|_,_|___|_|.dev | Command: \`socket oops\`, cwd: <redacted>"
54+
`)
55+
56+
expect(code, 'dry-run should exit with code 0 if input ok').toBe(0)
57+
}
58+
)
59+
})

src/commands/ci/cmd-ci.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { logger } from '@socketsecurity/registry/lib/logger'
2+
3+
import { handleCI } from './handle-ci'
4+
import constants from '../../constants'
5+
import { commonFlags } from '../../flags'
6+
import { meowOrExit } from '../../utils/meow-with-subcommands'
7+
8+
import type { CliCommandConfig } from '../../utils/meow-with-subcommands'
9+
10+
const { DRY_RUN_BAIL_TEXT } = constants
11+
12+
const config: CliCommandConfig = {
13+
commandName: 'ci',
14+
description:
15+
'Create a new scan and report whether it passes your security policy',
16+
hidden: true,
17+
flags: {
18+
...commonFlags
19+
},
20+
help: (parentName, _config) => `
21+
Usage
22+
$ ${parentName}
23+
24+
This command is intended to use in CI runs to allow automated systems to
25+
accept or reject a current build. When the scan does not pass your security
26+
policy, the exit code will be non-zero.
27+
28+
It will use the default org for the set API token.
29+
`
30+
}
31+
32+
export const cmdCI = {
33+
description: config.description,
34+
hidden: config.hidden,
35+
run
36+
}
37+
38+
async function run(
39+
argv: string[] | readonly string[],
40+
importMeta: ImportMeta,
41+
{ parentName }: { parentName: string }
42+
): Promise<void> {
43+
const cli = meowOrExit({
44+
argv,
45+
config,
46+
importMeta,
47+
parentName
48+
})
49+
50+
if (cli.flags['dryRun']) {
51+
logger.log(DRY_RUN_BAIL_TEXT)
52+
return
53+
}
54+
55+
await handleCI()
56+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { logger } from '@socketsecurity/registry/lib/logger'
2+
3+
import { handleApiCall } from '../../utils/api'
4+
import { getConfigValue } from '../../utils/config'
5+
import { setupSdk } from '../../utils/sdk'
6+
7+
// Use the config defaultOrg when set, otherwise discover from remote
8+
export async function getDefaultOrgSlug(): Promise<string | void> {
9+
let defaultOrg = getConfigValue('defaultOrg')
10+
if (defaultOrg) {
11+
logger.info(`Using default org: ${defaultOrg}`)
12+
} else {
13+
const sockSdk = await setupSdk()
14+
const result = await handleApiCall(
15+
sockSdk.getOrganizations(),
16+
'looking up organizations'
17+
)
18+
// Ignore a failed request here. It was not the primary goal of
19+
// running this command and reporting it only leads to end-user confusion.
20+
if (!result.success) {
21+
logger.fail(
22+
'Failed to fetch default organization from API. Unable to continue.'
23+
)
24+
process.exitCode = 1
25+
return
26+
}
27+
const orgs = result.data.organizations
28+
const keys = Object.keys(orgs)
29+
30+
if (!keys[0]) {
31+
logger.fail(
32+
'Could not find default organization for the current API token. Unable to continue.'
33+
)
34+
process.exitCode = 1
35+
return
36+
}
37+
38+
const slug = (keys[0] in orgs && orgs?.[keys[0]]?.name) ?? undefined
39+
40+
if (slug) {
41+
defaultOrg = slug
42+
logger.info(`Resolved org to: ${defaultOrg}`)
43+
}
44+
}
45+
46+
if (!defaultOrg) {
47+
logger.fail(
48+
'Could not find the default organization for the current API token. Unable to continue.'
49+
)
50+
process.exitCode = 1
51+
return
52+
}
53+
54+
return defaultOrg
55+
}

src/commands/ci/handle-ci.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { getDefaultOrgSlug } from './fetch-default-org-slug'
2+
import { handleCreateNewScan } from '../scan/handle-create-new-scan'
3+
4+
export async function handleCI(): Promise<void> {
5+
// ci: {
6+
// description: 'Alias for "report create --view --strict"',
7+
// argv: ['report', 'create', '--view', '--strict']
8+
// }
9+
const orgSlug = await getDefaultOrgSlug()
10+
if (!orgSlug) {
11+
return
12+
}
13+
14+
// TODO: does it make sense to discover the commit details from local git?
15+
// TODO: does it makes sense to use custom branch/repo names here? probably socket.yml, right
16+
await handleCreateNewScan({
17+
branchName: 'socket-default-branch',
18+
commitMessage: '',
19+
commitHash: '',
20+
committers: '',
21+
cwd: process.cwd(),
22+
defaultBranch: false,
23+
orgSlug,
24+
outputKind: 'json',
25+
pendingHead: true, // when true, requires branch name set, tmp false
26+
pullRequest: 0,
27+
repoName: 'socket-default-repository',
28+
readOnly: false,
29+
report: true,
30+
targets: ['.'],
31+
tmp: false // don't set when pendingHead is true
32+
})
33+
}

src/commands/scan/cmd-scan-create.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ describe('socket scan create', async () => {
6262
--readOnly Similar to --dry-run except it can read from remote, stops before it would create an actual report
6363
--repo Repository name
6464
--report Wait for the scan creation to complete, then basically run \`socket scan report\` on it
65-
--tmp Set the visibility (true/false) of the scan in your dashboard
65+
--tmp Set the visibility (true/false) of the scan in your dashboard. Can not be used when --pendingHead is set.
6666
6767
Examples
6868
$ socket scan create --repo=test-repo --branch=main FakeOrg ./package.json"

src/commands/scan/cmd-scan-create.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ const config: CliCommandConfig = {
9595
shortFlag: 't',
9696
default: false,
9797
description:
98-
'Set the visibility (true/false) of the scan in your dashboard'
98+
'Set the visibility (true/false) of the scan in your dashboard. Can not be used when --pendingHead is set.'
9999
}
100100
},
101101
// TODO: your project's "socket.yml" file's "projectIgnorePaths"
@@ -154,7 +154,7 @@ async function run(
154154
})
155155

156156
const {
157-
branch: branchName = '',
157+
branch: branchName = 'socket-default-branch',
158158
commitHash,
159159
commitMessage,
160160
committers,
@@ -166,7 +166,7 @@ async function run(
166166
pendingHead,
167167
pullRequest,
168168
readOnly,
169-
repo: repoName = '',
169+
repo: repoName = 'socket-default-repository',
170170
report,
171171
tmp
172172
} = cli.flags as {
@@ -264,6 +264,27 @@ async function run(
264264
message: 'This command requires an API token for access',
265265
pass: 'ok',
266266
fail: 'missing (try `socket login`)'
267+
},
268+
{
269+
nook: true,
270+
test: !pendingHead || !tmp,
271+
message: 'Can not use --pendingHead and --tmp at the same time',
272+
pass: 'ok',
273+
fail: 'remove at least one flag'
274+
},
275+
{
276+
nook: true,
277+
test: !pendingHead || !!branchName,
278+
message: 'When --pendingHead is set, --branch is mandatory',
279+
pass: 'ok',
280+
fail: 'missing branch name'
281+
},
282+
{
283+
nook: true,
284+
test: !defaultBranch || !!branchName,
285+
message: 'When --defaultBranch is set, --branch is mandatory',
286+
pass: 'ok',
287+
fail: 'missing branch name'
267288
}
268289
)
269290
if (wasBadInput) {

0 commit comments

Comments
 (0)