Skip to content

Commit 914b14b

Browse files
committed
[package score] Add replacement for info command
1 parent fdc5814 commit 914b14b

File tree

9 files changed

+536
-1
lines changed

9 files changed

+536
-1
lines changed

src/cli.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { cmdNpx } from './commands/npx/cmd-npx'
2525
import { cmdOops } from './commands/oops/cmd-oops'
2626
import { cmdOptimize } from './commands/optimize/cmd-optimize'
2727
import { cmdOrganization } from './commands/organization/cmd-organization'
28+
import { cmdPackage } from './commands/package/cmd-package'
2829
import { cmdRawNpm } from './commands/raw-npm/cmd-raw-npm'
2930
import { cmdRawNpx } from './commands/raw-npx/cmd-raw-npx'
3031
import { cmdReport } from './commands/report/cmd-report'
@@ -61,6 +62,7 @@ void (async () => {
6162
oops: cmdOops,
6263
optimize: cmdOptimize,
6364
organization: cmdOrganization,
65+
package: cmdPackage,
6466
'raw-npm': cmdRawNpm,
6567
'raw-npx': cmdRawNpx,
6668
report: cmdReport,
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
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 package score', async () => {
11+
// Lazily access constants.rootBinPath.
12+
const entryPath = path.join(constants.rootBinPath, `${CLI}.js`)
13+
14+
cmdit(['package', 'score', '--help'], 'should support --help', async cmd => {
15+
const { code, stderr, stdout } = await invokeNpm(entryPath, cmd)
16+
expect(stdout).toMatchInlineSnapshot(
17+
`
18+
"Look up info regarding a package
19+
20+
Usage
21+
$ socket package score <<ecosystem> <name> [<name> ...] | <purl> [<purl> ...]>
22+
23+
Options
24+
--dryRun Do input validation for a command and exit 0 when input is ok
25+
--help Print this help.
26+
--json Output result as json
27+
--markdown Output result as markdown
28+
29+
Show scoring details for one or more packages.
30+
Only a few ecosystems are supported like npm, golang, and maven.
31+
32+
If the first arg is an ecosystem, remaining args that are not a "purl" are
33+
assumed to be scoped in that ecosystem. If the first arg is in "purl" form
34+
then all args must be in purl form ("package url": \`pkg:eco/name@version\`).
35+
36+
This command takes 100 quota units.
37+
This command requires \`packages:list\` scope access on your API token.
38+
39+
Examples
40+
$ socket package score npm webtorrent
41+
$ socket package score npm webtorrent@1.9.1
42+
$ socket package score npm/webtorrent@1.9.1
43+
$ socket package score maven webtorrent babel
44+
$ socket package score npm/webtorrent golang/babel
45+
$ socket package score npm npm/webtorrent@1.0.1 babel"
46+
`
47+
)
48+
expect(`\n ${stderr}`).toMatchInlineSnapshot(`
49+
"
50+
_____ _ _ /---------------
51+
| __|___ ___| |_ ___| |_ | Socket.dev CLI ver <redacted>
52+
|__ | . | _| '_| -_| _| | Node: <redacted>, API token set: <redacted>
53+
|_____|___|___|_,_|___|_|.dev | Command: \`socket package score\`, cwd: <redacted>"
54+
`)
55+
56+
expect(code, 'help should exit with code 2').toBe(2)
57+
expect(stderr, 'header should include command (without params)').toContain(
58+
'`socket package score`'
59+
)
60+
})
61+
62+
cmdit(
63+
['package', 'score', '--dry-run'],
64+
'should require args with just dry-run',
65+
async cmd => {
66+
const { code, stderr, stdout } = await invokeNpm(entryPath, cmd)
67+
expect(stdout).toMatchInlineSnapshot(`""`)
68+
expect(`\n ${stderr}`).toMatchInlineSnapshot(`
69+
"
70+
_____ _ _ /---------------
71+
| __|___ ___| |_ ___| |_ | Socket.dev CLI ver <redacted>
72+
|__ | . | _| '_| -_| _| | Node: <redacted>, API token set: <redacted>
73+
|_____|___|___|_,_|___|_|.dev | Command: \`socket package score\`, cwd: <redacted>
74+
75+
\\x1b[31m\\xd7\\x1b[39m \\x1b[41m\\x1b[37mInput error\\x1b[39m\\x1b[49m: Please provide the required fields:
76+
77+
- Expecting an ecosystem \\x1b[31m(missing!)\\x1b[39m
78+
79+
- Expecting at least one package \\x1b[31m(missing!)\\x1b[39m"
80+
`)
81+
82+
expect(code, 'dry-run should exit with code 2 if missing input').toBe(2)
83+
}
84+
)
85+
86+
cmdit(
87+
['package', 'score', 'npm', 'babel', '--dry-run'],
88+
'should require args with just dry-run',
89+
async cmd => {
90+
const { code, stderr, stdout } = await invokeNpm(entryPath, cmd)
91+
expect(stdout).toMatchInlineSnapshot(`"[DryRun]: Bailing now"`)
92+
expect(`\n ${stderr}`).toMatchInlineSnapshot(`
93+
"
94+
_____ _ _ /---------------
95+
| __|___ ___| |_ ___| |_ | Socket.dev CLI ver <redacted>
96+
|__ | . | _| '_| -_| _| | Node: <redacted>, API token set: <redacted>
97+
|_____|___|___|_,_|___|_|.dev | Command: \`socket package score\`, cwd: <redacted>"
98+
`)
99+
100+
expect(code, 'dry-run should exit with code 0 if input ok').toBe(0)
101+
}
102+
)
103+
})
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import colors from 'yoctocolors-cjs'
2+
3+
import { logger } from '@socketsecurity/registry/lib/logger'
4+
5+
import { showPurlInfo } from './show-purl-info'
6+
import constants from '../../constants'
7+
import { commonFlags, outputFlags } from '../../flags'
8+
import { meowOrExit } from '../../utils/meow-with-subcommands'
9+
import { getFlagListOutput } from '../../utils/output-formatting'
10+
11+
import type { CliCommandConfig } from '../../utils/meow-with-subcommands'
12+
13+
const { DRY_RUN_BAIL_TEXT } = constants
14+
15+
const config: CliCommandConfig = {
16+
commandName: 'score',
17+
description: 'Look up info regarding a package',
18+
hidden: false,
19+
flags: {
20+
...commonFlags,
21+
...outputFlags
22+
},
23+
help: (command, config) => `
24+
Usage
25+
$ ${command} <<ecosystem> <name> [<name> ...] | <purl> [<purl> ...]>
26+
27+
Options
28+
${getFlagListOutput(config.flags, 6)}
29+
30+
Show scoring details for one or more packages.
31+
Only a few ecosystems are supported like npm, golang, and maven.
32+
33+
If the first arg is an ecosystem, remaining args that are not a "purl" are
34+
assumed to be scoped in that ecosystem. If the first arg is in "purl" form
35+
then all args must be in purl form ("package url": \`pkg:eco/name@version\`).
36+
37+
This command takes 100 quota units.
38+
This command requires \`packages:list\` scope access on your API token.
39+
40+
Examples
41+
$ ${command} npm webtorrent
42+
$ ${command} npm webtorrent@1.9.1
43+
$ ${command} npm/webtorrent@1.9.1
44+
$ ${command} maven webtorrent babel
45+
$ ${command} npm/webtorrent golang/babel
46+
$ ${command} npm npm/webtorrent@1.0.1 babel
47+
`
48+
}
49+
50+
export const cmdPackageScore = {
51+
description: config.description,
52+
hidden: config.hidden,
53+
run
54+
}
55+
56+
async function run(
57+
argv: string[] | readonly string[],
58+
importMeta: ImportMeta,
59+
{ parentName }: { parentName: string }
60+
): Promise<void> {
61+
const cli = meowOrExit({
62+
argv,
63+
config,
64+
importMeta,
65+
parentName
66+
})
67+
68+
const { json, markdown } = cli.flags
69+
const [ecosystem, ...pkgs] = cli.input
70+
71+
if (!ecosystem || !pkgs.length || !pkgs[0]) {
72+
// Use exit status of 2 to indicate incorrect usage, generally invalid
73+
// options or missing arguments.
74+
// https://www.gnu.org/software/bash/manual/html_node/Exit-Status.html
75+
process.exitCode = 2
76+
logger.fail(`${colors.bgRed(colors.white('Input error'))}: Please provide the required fields:\n
77+
- Expecting an ecosystem ${!ecosystem ? colors.red('(missing!)') : colors.green('(ok)')}\n
78+
- Expecting at least one package ${!pkgs.length || !pkgs[0] ? colors.red('(missing!)') : colors.green('(ok)')}\n
79+
`)
80+
return
81+
}
82+
83+
const purls = pkgs.map(pkg => {
84+
return 'pkg:' + ecosystem + '/' + pkg
85+
})
86+
87+
if (cli.flags['dryRun']) {
88+
logger.log(DRY_RUN_BAIL_TEXT)
89+
return
90+
}
91+
92+
await showPurlInfo({
93+
// commandName: `${parentName} ${config.commandName}`,
94+
// includeAllIssues: Boolean(all),
95+
outputKind: json ? 'json' : markdown ? 'markdown' : 'text',
96+
purls
97+
// strict: Boolean(strict)
98+
})
99+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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 manifest', async () => {
11+
// Lazily access constants.rootBinPath.
12+
const entryPath = path.join(constants.rootBinPath, `${CLI}.js`)
13+
14+
cmdit(['manifest', '--help'], 'should support --help', async cmd => {
15+
const { code, stderr, stdout } = await invokeNpm(entryPath, cmd)
16+
expect(stdout).toMatchInlineSnapshot(
17+
`
18+
"Generate a dependency manifest for given file or dir
19+
20+
Usage
21+
$ socket manifest <command>
22+
23+
Commands
24+
auto Auto-detect build and attempt to generate manifest file
25+
gradle [beta] Use Gradle to generate a manifest file (\`pom.xml\`) for a Gradle/Java/Kotlin/etc project
26+
kotlin [beta] Use Gradle to generate a manifest file (\`pom.xml\`) for a Kotlin project
27+
scala [beta] Generate a manifest file (\`pom.xml\`) from Scala's \`build.sbt\` file
28+
29+
Options
30+
--dryRun Do input validation for a command and exit 0 when input is ok
31+
--help Print this help.
32+
33+
Examples
34+
$ socket manifest --help"
35+
`
36+
)
37+
expect(`\n ${stderr}`).toMatchInlineSnapshot(`
38+
"
39+
_____ _ _ /---------------
40+
| __|___ ___| |_ ___| |_ | Socket.dev CLI ver <redacted>
41+
|__ | . | _| '_| -_| _| | Node: <redacted>, API token set: <redacted>
42+
|_____|___|___|_,_|___|_|.dev | Command: \`socket manifest\`, cwd: <redacted>"
43+
`)
44+
45+
expect(code, 'help should exit with code 2').toBe(2)
46+
expect(stderr, 'header should include command (without params)').toContain(
47+
'`socket manifest`'
48+
)
49+
})
50+
51+
cmdit(
52+
['manifest', 'mootools', '--dry-run'],
53+
'should require args with just dry-run',
54+
async cmd => {
55+
const { code, stderr, stdout } = await invokeNpm(entryPath, cmd)
56+
expect(stdout).toMatchInlineSnapshot(
57+
`"[DryRun]: No-op, call a sub-command; ok"`
58+
)
59+
expect(`\n ${stderr}`).toMatchInlineSnapshot(`
60+
"
61+
_____ _ _ /---------------
62+
| __|___ ___| |_ ___| |_ | Socket.dev CLI ver <redacted>
63+
|__ | . | _| '_| -_| _| | Node: <redacted>, API token set: <redacted>
64+
|_____|___|___|_,_|___|_|.dev | Command: \`socket manifest\`, cwd: <redacted>"
65+
`)
66+
67+
expect(code, 'dry-run should exit with code 0 if input ok').toBe(0)
68+
}
69+
)
70+
})
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { cmdPackageScore } from './cmd-package-score'
2+
import { meowWithSubcommands } from '../../utils/meow-with-subcommands'
3+
4+
import type { CliSubcommand } from '../../utils/meow-with-subcommands'
5+
6+
const description = 'Commands relating to looking up published packages'
7+
8+
export const cmdPackage: CliSubcommand = {
9+
description,
10+
hidden: true, // [beta]
11+
async run(argv, importMeta, { parentName }) {
12+
await meowWithSubcommands(
13+
{
14+
score: cmdPackageScore
15+
},
16+
{
17+
aliases: {
18+
pkg: {
19+
description,
20+
hidden: true,
21+
argv: []
22+
}
23+
},
24+
argv,
25+
description,
26+
importMeta,
27+
name: parentName + ' package'
28+
}
29+
)
30+
}
31+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { logger } from '@socketsecurity/registry/lib/logger'
2+
import { SocketSdkResultType, SocketSdkReturnType } from '@socketsecurity/sdk'
3+
4+
import constants from '../../constants'
5+
import { handleApiCall, handleUnsuccessfulApiResponse } from '../../utils/api'
6+
import { getPublicToken, setupSdk } from '../../utils/sdk'
7+
8+
export async function fetchPackageInfo(
9+
purls: string[]
10+
): Promise<SocketSdkReturnType<'batchPackageFetch'>> {
11+
const socketSdk = await setupSdk(getPublicToken())
12+
13+
// Lazily access constants.spinner.
14+
const { spinner } = constants
15+
16+
logger.error(
17+
`Requesting data for ${purls.length} package urls (purl): ${purls.join(', ')}`
18+
)
19+
spinner.start(`Requesting data ...`)
20+
21+
const result: Awaited<SocketSdkResultType<'batchPackageFetch'>> =
22+
await handleApiCall(
23+
socketSdk.batchPackageFetch(
24+
{
25+
alerts: 'true'
26+
// compact: false,
27+
// fixable: false,
28+
// licenseattrib: false,
29+
// licensedetails: false
30+
},
31+
{ components: purls.map(purl => ({ purl })) }
32+
),
33+
'looking up package'
34+
)
35+
36+
spinner.successAndStop('Request completed')
37+
38+
if (result.success) {
39+
return result
40+
} else {
41+
handleUnsuccessfulApiResponse('batchPackageFetch', result)
42+
}
43+
}

0 commit comments

Comments
 (0)