@socketsecurity/cli 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2022 Pelle Wessman
3
+ Copyright (c) 2022 Socket Inc
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -15,25 +15,51 @@ npm install -g @socketsecurity/cli
15
15
  ```bash
16
16
  socket --help
17
17
  socket info webtorrent@1.9.1
18
- socket report create package.json
18
+ socket report create package.json --view
19
+ socket report view QXU8PmK7LfH608RAwfIKdbcHgwEd_ZeWJ9QEGv05FJUQ
19
20
  ```
20
21
 
21
22
  ## Commands
22
23
 
23
- * `info <package@version>` - looks up issues for a package
24
- * `report create` - creates a report
24
+ * `socket info <package@version>` - looks up issues for a package
25
+ * `socket report create <path(s)-to-folder-or-file>` - uploads the specified `package.json` and/or `package-lock.json` to create a report on [socket.dev](https://socket.dev/). If only one of a `package.json`/`package-lock.json` has been specified, the other will be automatically found and uploaded if it exists
26
+ * `socket report view <report-id>` - looks up issues and scores from a report
27
+
28
+ ## Flags
29
+
30
+ ### Command specific flags
31
+
32
+ * `--view` - when set on `socket report create` the command will immediately do a `socket report view` style view of the created report, waiting for the server to complete it
33
+
34
+ ### Output flags
35
+
36
+ * `--json` - outputs result as json which you can then pipe into [`jq`](https://stedolan.github.io/jq/) and other tools
37
+ * `--markdown` - outputs result as markdown which you can then copy into an issue, PR or even chat
38
+
39
+ ### Other flags
40
+
41
+ * `--dry-run` - like all CLI tools that perform an action should have, we have a dry run flag. Eg. `socket report create` supports running the command without actually uploading anything
42
+ * `--debug` - outputs additional debug output. Great for debugging, geeks and us who develop. Hopefully you will never _need_ it, but it can still be fun, right?
43
+ * `--help` - prints the help for the current command. All CLI tools should have this flag
44
+ * `--version` - prints the version of the tool. All CLI tools should have this flag
25
45
 
26
46
  ## Environment variables
27
47
 
28
48
  * `SOCKET_SECURITY_API_KEY` - if set, this will be used as the API-key
29
49
 
50
+ ## Contributing
51
+
30
52
  ### Environment variables for development
31
53
 
32
54
  * `SOCKET_SECURITY_API_BASE_URL` - if set, this will be the base for all API-calls. Defaults to `https://api.socket.dev/v0/`
33
55
  * `SOCKET_SECURITY_API_PROXY` - if set to something like [`http://127.0.0.1:9090`](https://docs.proxyman.io/troubleshooting/couldnt-see-any-requests-from-3rd-party-network-libraries), then all request will be proxied through that proxy
34
56
 
57
+ ## Similar projects
58
+
59
+ * [`@socketsecurity/sdk`](https://github.com/SocketDev/socket-sdk-js) - the SDK used in this CLI
60
+
35
61
  ## See also
36
62
 
37
- * [`@socketsecurity/sdk`]('https://github.com/SocketDev/socket-sdk-js") - the SDK used in this CLI
63
+ * [Announcement blog post](https://socket.dev/blog/announcing-socket-cli-preview)
38
64
  * [Socket API Reference](https://docs.socket.dev/reference) - the API used in this CLI
39
65
  * [Socket GitHub App](https://github.com/apps/socket-security) - the plug-and-play GitHub App
@@ -3,27 +3,45 @@
3
3
  import chalk from 'chalk'
4
4
  import meow from 'meow'
5
5
  import ora from 'ora'
6
- import { ErrorWithCause } from 'pony-cause'
7
6
 
7
+ import { handleApiCall, handleUnsuccessfulApiResponse } from '../../utils/api-helpers.js'
8
8
  import { ChalkOrMarkdown } from '../../utils/chalk-markdown.js'
9
- import { AuthError, InputError } from '../../utils/errors.js'
9
+ import { InputError } from '../../utils/errors.js'
10
+ import { getSeveritySummary } from '../../utils/format-issues.js'
10
11
  import { printFlagList } from '../../utils/formatting.js'
11
- import { stringJoinWithSeparateFinalSeparator } from '../../utils/misc.js'
12
12
  import { setupSdk } from '../../utils/sdk.js'
13
13
 
14
- const description = 'Look up info regarding a package'
14
+ /** @type {import('../../utils/meow-with-subcommands').CliSubcommand} */
15
+ export const info = {
16
+ description: 'Look up info regarding a package',
17
+ async run (argv, importMeta, { parentName }) {
18
+ const name = parentName + ' info'
19
+
20
+ const input = setupCommand(name, info.description, argv, importMeta)
21
+ const result = input && await fetchPackageData(input.pkgName, input.pkgVersion)
22
+
23
+ if (result) {
24
+ formatPackageDataOutput(result.data, { name, ...input })
25
+ }
26
+ }
27
+ }
15
28
 
16
- /** @type {import('../../utils/meow-with-subcommands').CliSubcommandRun} */
17
- const run = async (argv, importMeta, { parentName }) => {
18
- const name = parentName + ' info'
29
+ // Internal functions
19
30
 
31
+ /**
32
+ * @param {string} name
33
+ * @param {string} description
34
+ * @param {readonly string[]} argv
35
+ * @param {ImportMeta} importMeta
36
+ * @returns {void|{ outputJson: boolean, outputMarkdown: boolean, pkgName: string, pkgVersion: string }}
37
+ */
38
+ function setupCommand (name, description, argv, importMeta) {
20
39
  const cli = meow(`
21
40
  Usage
22
41
  $ ${name} <name>
23
42
 
24
43
  Options
25
44
  ${printFlagList({
26
- '--debug': 'Output debug information',
27
45
  '--json': 'Output result as json',
28
46
  '--markdown': 'Output result as markdown',
29
47
  }, 6)}
@@ -36,11 +54,6 @@ const run = async (argv, importMeta, { parentName }) => {
36
54
  description,
37
55
  importMeta,
38
56
  flags: {
39
- debug: {
40
- type: 'boolean',
41
- alias: 'd',
42
- default: false,
43
- },
44
57
  json: {
45
58
  type: 'boolean',
46
59
  alias: 'j',
@@ -83,69 +96,56 @@ const run = async (argv, importMeta, { parentName }) => {
83
96
  throw new InputError('Need to specify a version, like eg: webtorrent@1.0.0')
84
97
  }
85
98
 
86
- const socketSdk = await setupSdk()
99
+ return {
100
+ outputJson,
101
+ outputMarkdown,
102
+ pkgName,
103
+ pkgVersion
104
+ }
105
+ }
87
106
 
107
+ /**
108
+ * @param {string} pkgName
109
+ * @param {string} pkgVersion
110
+ * @returns {Promise<void|import('@socketsecurity/sdk').SocketSdkReturnType<'getIssuesByNPMPackage'>>}
111
+ */
112
+ async function fetchPackageData (pkgName, pkgVersion) {
113
+ const socketSdk = await setupSdk()
88
114
  const spinner = ora(`Looking up data for version ${pkgVersion} of ${pkgName}`).start()
89
-
90
- /** @type {Awaited<ReturnType<import('@socketsecurity/sdk').SocketSdk["getIssuesByNPMPackage"]>>} */
91
- let result
92
-
93
- try {
94
- result = await socketSdk.getIssuesByNPMPackage(pkgName, pkgVersion)
95
- } catch (cause) {
96
- spinner.fail()
97
- throw new ErrorWithCause('Failed to look up package', { cause })
98
- }
115
+ const result = await handleApiCall(socketSdk.getIssuesByNPMPackage(pkgName, pkgVersion), spinner, 'looking up package')
99
116
 
100
117
  if (result.success === false) {
101
- if (result.status === 401 || result.status === 403) {
102
- spinner.stop()
103
- throw new AuthError(result.error.message)
104
- }
105
- spinner.fail(chalk.white.bgRed('API returned an error:') + ' ' + result.error.message)
106
- process.exit(1)
118
+ return handleUnsuccessfulApiResponse(result, spinner)
107
119
  }
108
120
 
109
- const data = result.data
121
+ // Conclude the status of the API call
110
122
 
111
- /** @typedef {(typeof data)[number]["value"] extends infer U | undefined ? U : never} SocketSdkIssue */
112
- /** @type {Record<SocketSdkIssue["severity"], number>} */
113
- const severityCount = { low: 0, middle: 0, high: 0, critical: 0 }
114
- for (const issue of data) {
115
- const value = issue.value
116
-
117
- if (!value) {
118
- continue
119
- }
120
-
121
- if (severityCount[value.severity] !== undefined) {
122
- severityCount[value.severity] += 1
123
- }
124
- }
123
+ const issueSummary = getSeveritySummary(result.data)
124
+ spinner.succeed(`Found ${issueSummary || 'no'} issues for version ${pkgVersion} of ${pkgName}`)
125
125
 
126
- const issueSummary = stringJoinWithSeparateFinalSeparator([
127
- severityCount.critical ? severityCount.critical + ' critical' : undefined,
128
- severityCount.high ? severityCount.high + ' high' : undefined,
129
- severityCount.middle ? severityCount.middle + ' middle' : undefined,
130
- severityCount.low ? severityCount.low + ' low' : undefined,
131
- ])
126
+ return result
127
+ }
132
128
 
133
- spinner.succeed(`Found ${issueSummary || 'no'} issues for version ${pkgVersion} of ${pkgName}`)
129
+ /**
130
+ * @param {import('@socketsecurity/sdk').SocketSdkReturnType<'getIssuesByNPMPackage'>["data"]} data
131
+ * @param {{ name: string, outputJson: boolean, outputMarkdown: boolean, pkgName: string, pkgVersion: string }} context
132
+ * @returns {void}
133
+ */
134
+ function formatPackageDataOutput (data, { name, outputJson, outputMarkdown, pkgName, pkgVersion }) {
135
+ // If JSON, output and return...
134
136
 
135
137
  if (outputJson) {
136
138
  console.log(JSON.stringify(data, undefined, 2))
137
139
  return
138
140
  }
139
141
 
142
+ // ...else do the CLI / Markdown output dance
143
+
140
144
  const format = new ChalkOrMarkdown(!!outputMarkdown)
141
145
  const url = `https://socket.dev/npm/package/${pkgName}/overview/${pkgVersion}`
142
146
 
143
147
  console.log('\nDetailed info on socket.dev: ' + format.hyperlink(`${pkgName} v${pkgVersion}`, url, { fallbackToUrl: true }))
144
-
145
148
  if (!outputMarkdown) {
146
149
  console.log(chalk.dim('\nOr rerun', chalk.italic(name), 'using the', chalk.italic('--json'), 'flag to get full JSON output'))
147
150
  }
148
151
  }
149
-
150
- /** @type {import('../../utils/meow-with-subcommands').CliSubcommand} */
151
- export const info = { description, run }
@@ -3,24 +3,64 @@
3
3
  import { stat } from 'node:fs/promises'
4
4
  import path from 'node:path'
5
5
 
6
- import chalk from 'chalk'
7
6
  import meow from 'meow'
8
7
  import ora from 'ora'
9
8
  import { ErrorWithCause } from 'pony-cause'
10
9
 
10
+ import { handleApiCall, handleUnsuccessfulApiResponse } from '../../utils/api-helpers.js'
11
11
  import { ChalkOrMarkdown, logSymbols } from '../../utils/chalk-markdown.js'
12
- import { AuthError, InputError } from '../../utils/errors.js'
12
+ import { InputError } from '../../utils/errors.js'
13
13
  import { printFlagList } from '../../utils/formatting.js'
14
14
  import { createDebugLogger } from '../../utils/misc.js'
15
15
  import { setupSdk } from '../../utils/sdk.js'
16
16
  import { isErrnoException } from '../../utils/type-helpers.js'
17
+ import { fetchReportData, formatReportDataOutput } from './view.js'
17
18
 
18
- const description = 'Create a project report'
19
+ /** @type {import('../../utils/meow-with-subcommands').CliSubcommand} */
20
+ export const create = {
21
+ description: 'Create a project report',
22
+ async run (argv, importMeta, { parentName }) {
23
+ const name = parentName + ' create'
24
+
25
+ const input = await setupCommand(name, create.description, argv, importMeta)
26
+
27
+ if (input) {
28
+ const {
29
+ cwd,
30
+ debugLog,
31
+ dryRun,
32
+ outputJson,
33
+ outputMarkdown,
34
+ packagePaths,
35
+ view,
36
+ } = input
37
+
38
+ const result = input && await createReport(packagePaths, { cwd, debugLog, dryRun })
39
+
40
+ if (result && view) {
41
+ const reportId = result.data.id
42
+ const reportResult = input && await fetchReportData(reportId)
43
+
44
+ if (reportResult) {
45
+ formatReportDataOutput(reportResult.data, { name, outputJson, outputMarkdown, reportId })
46
+ }
47
+ } else if (result) {
48
+ formatReportCreationOutput(result.data, { outputJson, outputMarkdown })
49
+ }
50
+ }
51
+ }
52
+ }
19
53
 
20
- /** @type {import('../../utils/meow-with-subcommands').CliSubcommandRun} */
21
- const run = async (argv, importMeta, { parentName }) => {
22
- const name = parentName + ' create'
54
+ // Internal functions
23
55
 
56
+ /**
57
+ * @param {string} name
58
+ * @param {string} description
59
+ * @param {readonly string[]} argv
60
+ * @param {ImportMeta} importMeta
61
+ * @returns {Promise<void|{ cwd: string, debugLog: typeof console.error, dryRun: boolean, outputJson: boolean, outputMarkdown: boolean, packagePaths: string[], view: boolean }>}
62
+ */
63
+ async function setupCommand (name, description, argv, importMeta) {
24
64
  const cli = meow(`
25
65
  Usage
26
66
  $ ${name} <paths-to-package-folders-and-files>
@@ -31,12 +71,14 @@ const run = async (argv, importMeta, { parentName }) => {
31
71
  '--dry-run': 'Only output what will be done without actually doing it',
32
72
  '--json': 'Output result as json',
33
73
  '--markdown': 'Output result as markdown',
74
+ '--view': 'Will wait for and return the created report'
34
75
  }, 6)}
35
76
 
36
77
  Examples
37
78
  $ ${name} .
38
79
  $ ${name} ../package-lock.json
39
80
  $ ${name} /path/to/a/package.json /path/to/another/package.json
81
+ $ ${name} . --view --json
40
82
  `, {
41
83
  argv,
42
84
  description,
@@ -61,6 +103,11 @@ const run = async (argv, importMeta, { parentName }) => {
61
103
  alias: 'm',
62
104
  default: false,
63
105
  },
106
+ view: {
107
+ type: 'boolean',
108
+ alias: 'v',
109
+ default: false,
110
+ },
64
111
  }
65
112
  })
66
113
 
@@ -68,6 +115,7 @@ const run = async (argv, importMeta, { parentName }) => {
68
115
  dryRun,
69
116
  json: outputJson,
70
117
  markdown: outputMarkdown,
118
+ view,
71
119
  } = cli.flags
72
120
 
73
121
  if (!cli.input[0]) {
@@ -80,6 +128,23 @@ const run = async (argv, importMeta, { parentName }) => {
80
128
  const cwd = process.cwd()
81
129
  const packagePaths = await resolvePackagePaths(cwd, cli.input)
82
130
 
131
+ return {
132
+ cwd,
133
+ debugLog,
134
+ dryRun,
135
+ outputJson,
136
+ outputMarkdown,
137
+ packagePaths,
138
+ view,
139
+ }
140
+ }
141
+
142
+ /**
143
+ * @param {string[]} packagePaths
144
+ * @param {{ cwd: string, debugLog: typeof console.error, dryRun: boolean }} context
145
+ * @returns {Promise<void|import('@socketsecurity/sdk').SocketSdkReturnType<'createReport'>>}
146
+ */
147
+ async function createReport (packagePaths, { cwd, debugLog, dryRun }) {
83
148
  debugLog(`${logSymbols.info} Uploading:`, packagePaths.join(`\n${logSymbols.info} Uploading:`))
84
149
 
85
150
  if (dryRun) {
@@ -87,43 +152,36 @@ const run = async (argv, importMeta, { parentName }) => {
87
152
  }
88
153
 
89
154
  const socketSdk = await setupSdk()
90
-
91
155
  const spinner = ora(`Creating report with ${packagePaths.length} package files`).start()
92
-
93
- /** @type {Awaited<ReturnType<typeof socketSdk.createReportFromFilePaths>>} */
94
- let result
95
-
96
- try {
97
- result = await socketSdk.createReportFromFilePaths(packagePaths, cwd)
98
- } catch (cause) {
99
- spinner.fail()
100
- throw new ErrorWithCause('Failed creating report', { cause })
101
- }
156
+ const result = await handleApiCall(socketSdk.createReportFromFilePaths(packagePaths, cwd), spinner, 'creating report')
102
157
 
103
158
  if (result.success === false) {
104
- if (result.status === 401 || result.status === 403) {
105
- spinner.stop()
106
- throw new AuthError(result.error.message)
107
- }
108
- spinner.fail(chalk.white.bgRed('API returned an error:') + ' ' + result.error.message)
109
- process.exit(1)
159
+ return handleUnsuccessfulApiResponse(result, spinner)
110
160
  }
111
161
 
162
+ // Conclude the status of the API call
163
+
112
164
  spinner.succeed()
113
165
 
166
+ return result
167
+ }
168
+
169
+ /**
170
+ * @param {import('@socketsecurity/sdk').SocketSdkReturnType<'createReport'>["data"]} data
171
+ * @param {{ outputJson: boolean, outputMarkdown: boolean }} context
172
+ * @returns {void}
173
+ */
174
+ function formatReportCreationOutput (data, { outputJson, outputMarkdown }) {
114
175
  if (outputJson) {
115
- console.log(JSON.stringify(result.data, undefined, 2))
176
+ console.log(JSON.stringify(data, undefined, 2))
116
177
  return
117
178
  }
118
179
 
119
180
  const format = new ChalkOrMarkdown(!!outputMarkdown)
120
181
 
121
- console.log('\nNew report: ' + format.hyperlink(result.data.id, result.data.url, { fallbackToUrl: true }))
182
+ console.log('\nNew report: ' + format.hyperlink(data.id, data.url, { fallbackToUrl: true }))
122
183
  }
123
184
 
124
- /** @type {import('../../utils/meow-with-subcommands').CliSubcommand} */
125
- export const create = { description, run }
126
-
127
185
  // TODO: Add globbing support with support for ignoring, as a "./**/package.json" in a project also traverses eg. node_modules
128
186
  /**
129
187
  * Takes paths to folders and/or package.json / package-lock.json files and resolves to package.json + package-lock.json pairs (where feasible)
@@ -1,5 +1,6 @@
1
1
  import { meowWithSubcommands } from '../../utils/meow-with-subcommands.js'
2
2
  import { create } from './create.js'
3
+ import { view } from './view.js'
3
4
 
4
5
  const description = 'Project report related commands'
5
6
 
@@ -10,6 +11,7 @@ export const report = {
10
11
  await meowWithSubcommands(
11
12
  {
12
13
  create,
14
+ view,
13
15
  },
14
16
  {
15
17
  argv,
@@ -0,0 +1,146 @@
1
+ /* eslint-disable no-console */
2
+
3
+ import chalk from 'chalk'
4
+ import meow from 'meow'
5
+ import ora from 'ora'
6
+
7
+ import { handleApiCall, handleUnsuccessfulApiResponse } from '../../utils/api-helpers.js'
8
+ import { ChalkOrMarkdown } from '../../utils/chalk-markdown.js'
9
+ import { InputError } from '../../utils/errors.js'
10
+ import { getSeveritySummary } from '../../utils/format-issues.js'
11
+ import { printFlagList } from '../../utils/formatting.js'
12
+ import { setupSdk } from '../../utils/sdk.js'
13
+
14
+ /** @type {import('../../utils/meow-with-subcommands').CliSubcommand} */
15
+ export const view = {
16
+ description: 'View a project report',
17
+ async run (argv, importMeta, { parentName }) {
18
+ const name = parentName + ' view'
19
+
20
+ const input = setupCommand(name, view.description, argv, importMeta)
21
+ const result = input && await fetchReportData(input.reportId)
22
+
23
+ if (result) {
24
+ formatReportDataOutput(result.data, { name, ...input })
25
+ }
26
+ }
27
+ }
28
+
29
+ // Internal functions
30
+
31
+ /**
32
+ * @param {string} name
33
+ * @param {string} description
34
+ * @param {readonly string[]} argv
35
+ * @param {ImportMeta} importMeta
36
+ * @returns {void|{ outputJson: boolean, outputMarkdown: boolean, reportId: string }}
37
+ */
38
+ function setupCommand (name, description, argv, importMeta) {
39
+ const cli = meow(`
40
+ Usage
41
+ $ ${name} <report-identifier>
42
+
43
+ Options
44
+ ${printFlagList({
45
+ '--json': 'Output result as json',
46
+ '--markdown': 'Output result as markdown',
47
+ }, 6)}
48
+
49
+ Examples
50
+ $ ${name} QXU8PmK7LfH608RAwfIKdbcHgwEd_ZeWJ9QEGv05FJUQ
51
+ `, {
52
+ argv,
53
+ description,
54
+ importMeta,
55
+ flags: {
56
+ debug: {
57
+ type: 'boolean',
58
+ alias: 'd',
59
+ default: false,
60
+ },
61
+ json: {
62
+ type: 'boolean',
63
+ alias: 'j',
64
+ default: false,
65
+ },
66
+ markdown: {
67
+ type: 'boolean',
68
+ alias: 'm',
69
+ default: false,
70
+ },
71
+ }
72
+ })
73
+
74
+ // Extract the input
75
+
76
+ const {
77
+ json: outputJson,
78
+ markdown: outputMarkdown,
79
+ } = cli.flags
80
+
81
+ const [reportId, ...extraInput] = cli.input
82
+
83
+ if (!reportId) {
84
+ cli.showHelp()
85
+ return
86
+ }
87
+
88
+ // Validate the input
89
+
90
+ if (extraInput.length) {
91
+ throw new InputError(`Can only handle a single report ID at a time, but got ${cli.input.length} report ID:s: ${cli.input.join(', ')}`)
92
+ }
93
+
94
+ return {
95
+ outputJson,
96
+ outputMarkdown,
97
+ reportId,
98
+ }
99
+ }
100
+
101
+ /**
102
+ * @param {string} reportId
103
+ * @returns {Promise<void|import('@socketsecurity/sdk').SocketSdkReturnType<'getReport'>>}
104
+ */
105
+ export async function fetchReportData (reportId) {
106
+ // Do the API call
107
+
108
+ const socketSdk = await setupSdk()
109
+ const spinner = ora(`Fetching report with ID ${reportId}`).start()
110
+ const result = await handleApiCall(socketSdk.getReport(reportId), spinner, 'fetching report')
111
+
112
+ if (result.success === false) {
113
+ return handleUnsuccessfulApiResponse(result, spinner)
114
+ }
115
+
116
+ // Conclude the status of the API call
117
+
118
+ const issueSummary = getSeveritySummary(result.data.issues)
119
+ spinner.succeed(`Report contains ${issueSummary || 'no'} issues`)
120
+
121
+ return result
122
+ }
123
+
124
+ /**
125
+ * @param {import('@socketsecurity/sdk').SocketSdkReturnType<'getReport'>["data"]} data
126
+ * @param {{ name: string, outputJson: boolean, outputMarkdown: boolean, reportId: string }} context
127
+ * @returns {void}
128
+ */
129
+ export function formatReportDataOutput (data, { name, outputJson, outputMarkdown, reportId }) {
130
+ // If JSON, output and return...
131
+
132
+ if (outputJson) {
133
+ console.log(JSON.stringify(data, undefined, 2))
134
+ return
135
+ }
136
+
137
+ // ...else do the CLI / Markdown output dance
138
+
139
+ const format = new ChalkOrMarkdown(!!outputMarkdown)
140
+ const url = `https://socket.dev/npm/reports/${encodeURIComponent(reportId)}`
141
+
142
+ console.log('\nDetailed info on socket.dev: ' + format.hyperlink(reportId, url, { fallbackToUrl: true }))
143
+ if (!outputMarkdown) {
144
+ console.log(chalk.dim('\nOr rerun', chalk.italic(name), 'using the', chalk.italic('--json'), 'flag to get full JSON output'))
145
+ }
146
+ }
@@ -0,0 +1,43 @@
1
+ import chalk from 'chalk'
2
+ import { ErrorWithCause } from 'pony-cause'
3
+
4
+ import { AuthError } from './errors.js'
5
+
6
+ /**
7
+ * @template T
8
+ * @param {import('@socketsecurity/sdk').SocketSdkErrorType<T>} result
9
+ * @param {import('ora').Ora} spinner
10
+ * @returns {void}
11
+ */
12
+ export function handleUnsuccessfulApiResponse (result, spinner) {
13
+ const resultError = 'error' in result && result.error && typeof result.error === 'object' ? result.error : {}
14
+ const message = 'message' in resultError && typeof resultError.message === 'string' ? resultError.message : 'No error message returned'
15
+
16
+ if (result.status === 401 || result.status === 403) {
17
+ spinner.stop()
18
+ throw new AuthError(message)
19
+ }
20
+ spinner.fail(chalk.white.bgRed('API returned an error:') + ' ' + message)
21
+ process.exit(1)
22
+ }
23
+
24
+ /**
25
+ * @template T
26
+ * @param {Promise<T>} value
27
+ * @param {import('ora').Ora} spinner
28
+ * @param {string} description
29
+ * @returns {Promise<T>}
30
+ */
31
+ export async function handleApiCall (value, spinner, description) {
32
+ /** @type {T} */
33
+ let result
34
+
35
+ try {
36
+ result = await value
37
+ } catch (cause) {
38
+ spinner.fail()
39
+ throw new ErrorWithCause(`Failed ${description}`, { cause })
40
+ }
41
+
42
+ return result
43
+ }
@@ -0,0 +1,44 @@
1
+ /** @typedef {import('@socketsecurity/sdk').SocketSdkReturnType<'getIssuesByNPMPackage'>['data']} SocketIssueList */
2
+ /** @typedef {SocketIssueList[number]['value'] extends infer U | undefined ? U : never} SocketIssue */
3
+
4
+ import { stringJoinWithSeparateFinalSeparator } from './misc.js'
5
+
6
+ /**
7
+ * @param {SocketIssueList} issues
8
+ * @returns {Record<SocketIssue['severity'], number>}
9
+ */
10
+ function getSeverityCount (issues) {
11
+ /** @type {Record<SocketIssue['severity'], number>} */
12
+ const severityCount = { low: 0, middle: 0, high: 0, critical: 0 }
13
+
14
+ for (const issue of issues) {
15
+ const value = issue.value
16
+
17
+ if (!value) {
18
+ continue
19
+ }
20
+
21
+ if (severityCount[value.severity] !== undefined) {
22
+ severityCount[value.severity] += 1
23
+ }
24
+ }
25
+
26
+ return severityCount
27
+ }
28
+
29
+ /**
30
+ * @param {SocketIssueList} issues
31
+ * @returns {string}
32
+ */
33
+ export function getSeveritySummary (issues) {
34
+ const severityCount = getSeverityCount(issues)
35
+
36
+ const issueSummary = stringJoinWithSeparateFinalSeparator([
37
+ severityCount.critical ? severityCount.critical + ' critical' : undefined,
38
+ severityCount.high ? severityCount.high + ' high' : undefined,
39
+ severityCount.middle ? severityCount.middle + ' middle' : undefined,
40
+ severityCount.low ? severityCount.low + ' low' : undefined,
41
+ ])
42
+
43
+ return issueSummary
44
+ }
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@socketsecurity/cli",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "CLI tool for Socket.dev",
5
- "homepage": "http://github.com/SocketDev/socket-commando",
5
+ "homepage": "http://github.com/SocketDev/socket-cli-js",
6
6
  "repository": {
7
7
  "type": "git",
8
- "url": "git://github.com/SocketDev/socket-commando.git"
8
+ "url": "git://github.com/SocketDev/socket-cli-js.git"
9
9
  },
10
10
  "keywords": [],
11
11
  "author": {
@@ -45,37 +45,37 @@
45
45
  "@types/mocha": "^10.0.0",
46
46
  "@types/node": "^14.18.31",
47
47
  "@types/prompts": "^2.4.1",
48
- "@typescript-eslint/eslint-plugin": "^5.36.2",
49
- "@typescript-eslint/parser": "^5.36.2",
48
+ "@typescript-eslint/eslint-plugin": "^5.44.0",
49
+ "@typescript-eslint/parser": "^5.44.0",
50
50
  "c8": "^7.12.0",
51
51
  "chai": "^4.3.6",
52
52
  "dependency-check": "^5.0.0-7",
53
- "eslint": "^8.23.0",
53
+ "eslint": "^8.28.0",
54
54
  "eslint-config-standard": "^17.0.0",
55
55
  "eslint-config-standard-jsx": "^11.0.0",
56
- "eslint-import-resolver-typescript": "^3.5.1",
56
+ "eslint-import-resolver-typescript": "^3.5.2",
57
57
  "eslint-plugin-import": "^2.26.0",
58
58
  "eslint-plugin-jsdoc": "^39.5.0",
59
- "eslint-plugin-n": "^15.3.0",
60
- "eslint-plugin-promise": "^6.0.1",
61
- "eslint-plugin-react": "^7.31.9",
59
+ "eslint-plugin-n": "^15.5.1",
60
+ "eslint-plugin-promise": "^6.1.1",
61
+ "eslint-plugin-react": "^7.31.11",
62
62
  "eslint-plugin-react-hooks": "^4.6.0",
63
63
  "husky": "^8.0.1",
64
- "installed-check": "^6.0.4",
64
+ "installed-check": "^6.0.5",
65
65
  "mocha": "^10.0.0",
66
66
  "npm-run-all2": "^6.0.2",
67
- "type-coverage": "^2.21.2",
68
- "typescript": "~4.8.4"
67
+ "type-coverage": "^2.24.1",
68
+ "typescript": "~4.9.3"
69
69
  },
70
70
  "dependencies": {
71
- "@socketsecurity/sdk": "^0.3.1",
71
+ "@socketsecurity/sdk": "^0.4.0",
72
72
  "chalk": "^5.1.2",
73
73
  "hpagent": "^1.2.0",
74
74
  "is-interactive": "^2.0.0",
75
75
  "is-unicode-supported": "^1.3.0",
76
76
  "meow": "^11.0.0",
77
77
  "ora": "^6.1.2",
78
- "pony-cause": "^2.1.4",
78
+ "pony-cause": "^2.1.8",
79
79
  "prompts": "^2.4.2",
80
80
  "terminal-link": "^3.0.0"
81
81
  }