@socketsecurity/cli 0.3.0 → 0.4.1

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,4 +1,4 @@
1
- The MIT License (MIT)
1
+ MIT License
2
2
 
3
3
  Copyright (c) 2022 Socket Inc
4
4
 
package/README.md CHANGED
@@ -1,5 +1,6 @@
1
1
  # Socket CLI
2
2
 
3
+ [![Socket Badge](https://socket.dev/api/badge/npm/package/@socketsecurity/cli)](https://socket.dev/npm/package/@socketsecurity/cli)
3
4
  [![npm version](https://img.shields.io/npm/v/@socketsecurity/cli.svg?style=flat)](https://www.npmjs.com/package/@socketsecurity/cli)
4
5
  [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](https://github.com/SocketDev/eslint-config)
5
6
  [![Follow @SocketSecurity](https://img.shields.io/twitter/follow/SocketSecurity?style=social)](https://twitter.com/SocketSecurity)
@@ -33,6 +34,12 @@ socket report view QXU8PmK7LfH608RAwfIKdbcHgwEd_ZeWJ9QEGv05FJUQ
33
34
 
34
35
  * `socket report view <report-id>` - looks up issues and scores from a report
35
36
 
37
+ ## Aliases
38
+
39
+ All aliases supports flags and arguments of the commands they alias.
40
+
41
+ * `socket ci` - alias for `socket report create --view --strict` which creates a report and quits with an exit code if the result is unhealthy. Use like eg. `socket ci .` for a report for the current folder
42
+
36
43
  ## Flags
37
44
 
38
45
  ### Command specific flags
@@ -47,7 +54,7 @@ socket report view QXU8PmK7LfH608RAwfIKdbcHgwEd_ZeWJ9QEGv05FJUQ
47
54
  ## Strictness flags
48
55
 
49
56
  * `--all` - by default only `high` and `critical` issues are included, by setting this flag all issues will be included
50
- * `--strict` - when set, exits with an error code if any issues were found
57
+ * `--strict` - when set, exits with an error code if report result is deemed unhealthy
51
58
 
52
59
  ### Other flags
53
60
 
package/cli.js CHANGED
@@ -18,6 +18,12 @@ try {
18
18
  await meowWithSubcommands(
19
19
  cliCommands,
20
20
  {
21
+ aliases: {
22
+ ci: {
23
+ description: 'Alias for "report create --view --strict"',
24
+ argv: ['report', 'create', '--view', '--strict']
25
+ },
26
+ },
21
27
  argv: process.argv.slice(2),
22
28
  name: 'socket',
23
29
  importMeta: import.meta
@@ -8,6 +8,7 @@ import meow from 'meow'
8
8
  import ora from 'ora'
9
9
  import { ErrorWithCause } from 'pony-cause'
10
10
 
11
+ import { fetchReportData, formatReportDataOutput } from './view.js'
11
12
  import { handleApiCall, handleUnsuccessfulApiResponse } from '../../utils/api-helpers.js'
12
13
  import { ChalkOrMarkdown, logSymbols } from '../../utils/chalk-markdown.js'
13
14
  import { InputError } from '../../utils/errors.js'
@@ -15,7 +16,6 @@ import { printFlagList } from '../../utils/formatting.js'
15
16
  import { createDebugLogger } from '../../utils/misc.js'
16
17
  import { getPackageFiles } from '../../utils/path-resolve.js'
17
18
  import { setupSdk } from '../../utils/sdk.js'
18
- import { fetchReportData, formatReportDataOutput } from './view.js'
19
19
 
20
20
  /** @type {import('../../utils/meow-with-subcommands').CliSubcommand} */
21
21
  export const create = {
@@ -27,6 +27,7 @@ export const create = {
27
27
 
28
28
  if (input) {
29
29
  const {
30
+ config,
30
31
  cwd,
31
32
  debugLog,
32
33
  dryRun,
@@ -38,7 +39,7 @@ export const create = {
38
39
  view,
39
40
  } = input
40
41
 
41
- const result = input && await createReport(packagePaths, { cwd, debugLog, dryRun })
42
+ const result = input && await createReport(packagePaths, { config, cwd, debugLog, dryRun })
42
43
 
43
44
  if (result && view) {
44
45
  const reportId = result.data.id
@@ -58,6 +59,7 @@ export const create = {
58
59
 
59
60
  /**
60
61
  * @typedef CommandContext
62
+ * @property {import('@socketsecurity/config').SocketYml|undefined} config
61
63
  * @property {string} cwd
62
64
  * @property {typeof console.error} debugLog
63
65
  * @property {boolean} dryRun
@@ -191,6 +193,7 @@ async function setupCommand (name, description, argv, importMeta) {
191
193
  const packagePaths = await getPackageFiles(cwd, cli.input, config, debugLog)
192
194
 
193
195
  return {
196
+ config,
194
197
  cwd,
195
198
  debugLog,
196
199
  dryRun,
@@ -205,10 +208,10 @@ async function setupCommand (name, description, argv, importMeta) {
205
208
 
206
209
  /**
207
210
  * @param {string[]} packagePaths
208
- * @param {Pick<CommandContext, 'cwd' | 'debugLog' | 'dryRun'>} context
211
+ * @param {Pick<CommandContext, 'config' | 'cwd' | 'debugLog' | 'dryRun'>} context
209
212
  * @returns {Promise<void|import('@socketsecurity/sdk').SocketSdkReturnType<'createReport'>>}
210
213
  */
211
- async function createReport (packagePaths, { cwd, debugLog, dryRun }) {
214
+ async function createReport (packagePaths, { config, cwd, debugLog, dryRun }) {
212
215
  debugLog('Uploading:', packagePaths.join(`\n${logSymbols.info} Uploading: `))
213
216
 
214
217
  if (dryRun) {
@@ -217,7 +220,8 @@ async function createReport (packagePaths, { cwd, debugLog, dryRun }) {
217
220
 
218
221
  const socketSdk = await setupSdk()
219
222
  const spinner = ora(`Creating report with ${packagePaths.length} package files`).start()
220
- const result = await handleApiCall(socketSdk.createReportFromFilePaths(packagePaths, cwd), spinner, 'creating report')
223
+ const apiCall = socketSdk.createReportFromFilePaths(packagePaths, cwd, config?.issueRules)
224
+ const result = await handleApiCall(apiCall, spinner, 'creating report')
221
225
 
222
226
  if (result.success === false) {
223
227
  return handleUnsuccessfulApiResponse(result, spinner)
@@ -1,6 +1,6 @@
1
- import { meowWithSubcommands } from '../../utils/meow-with-subcommands.js'
2
1
  import { create } from './create.js'
3
2
  import { view } from './view.js'
3
+ import { meowWithSubcommands } from '../../utils/meow-with-subcommands.js'
4
4
 
5
5
  const description = 'Project report related commands'
6
6
 
@@ -9,7 +9,6 @@ import { ChalkOrMarkdown } from '../../utils/chalk-markdown.js'
9
9
  import { InputError } from '../../utils/errors.js'
10
10
  import { getSeverityCount, formatSeverityCount } from '../../utils/format-issues.js'
11
11
  import { printFlagList } from '../../utils/formatting.js'
12
- import { objectSome } from '../../utils/misc.js'
13
12
  import { setupSdk } from '../../utils/sdk.js'
14
13
 
15
14
  /** @type {import('../../utils/meow-with-subcommands').CliSubcommand} */
@@ -56,7 +55,7 @@ function setupCommand (name, description, argv, importMeta) {
56
55
  '--all': 'Include all issues',
57
56
  '--json': 'Output result as json',
58
57
  '--markdown': 'Output result as markdown',
59
- '--strict': 'Exits with an error code if any matching issues are found',
58
+ '--strict': 'Exits with an error code if report result is deemed unhealthy',
60
59
  }, 6)}
61
60
 
62
61
  Examples
@@ -119,9 +118,7 @@ function setupCommand (name, description, argv, importMeta) {
119
118
  }
120
119
 
121
120
  /**
122
- * @typedef ReportData
123
- * @property {import('@socketsecurity/sdk').SocketSdkReturnType<'getReport'>["data"]} data
124
- * @property {Record<import('../../utils/format-issues').SocketIssue['severity'], number>} severityCount
121
+ * @typedef {import('@socketsecurity/sdk').SocketSdkReturnType<'getReport'>["data"]} ReportData
125
122
  */
126
123
 
127
124
  /**
@@ -142,27 +139,29 @@ export async function fetchReportData (reportId, { includeAllIssues, strict }) {
142
139
 
143
140
  // Conclude the status of the API call
144
141
 
145
- const severityCount = getSeverityCount(result.data.issues, includeAllIssues ? undefined : 'high')
146
-
147
- if (objectSome(severityCount)) {
142
+ if (strict) {
143
+ if (result.data.healthy) {
144
+ spinner.succeed('Report result is healthy and great!')
145
+ } else {
146
+ spinner.fail('Report result deemed unhealthy for project')
147
+ }
148
+ } else if (result.data.healthy === false) {
149
+ const severityCount = getSeverityCount(result.data.issues, includeAllIssues ? undefined : 'high')
148
150
  const issueSummary = formatSeverityCount(severityCount)
149
- spinner[strict ? 'fail' : 'succeed'](`Report has these issues: ${issueSummary}`)
151
+ spinner.succeed(`Report has these issues: ${issueSummary}`)
150
152
  } else {
151
153
  spinner.succeed('Report has no issues')
152
154
  }
153
155
 
154
- return {
155
- data: result.data,
156
- severityCount,
157
- }
156
+ return result.data
158
157
  }
159
158
 
160
159
  /**
161
- * @param {ReportData} reportData
160
+ * @param {ReportData} data
162
161
  * @param {{ name: string } & CommandContext} context
163
162
  * @returns {void}
164
163
  */
165
- export function formatReportDataOutput ({ data, severityCount }, { name, outputJson, outputMarkdown, reportId, strict }) {
164
+ export function formatReportDataOutput (data, { name, outputJson, outputMarkdown, reportId, strict }) {
166
165
  if (outputJson) {
167
166
  console.log(JSON.stringify(data, undefined, 2))
168
167
  } else {
@@ -175,7 +174,7 @@ export function formatReportDataOutput ({ data, severityCount }, { name, outputJ
175
174
  }
176
175
  }
177
176
 
178
- if (strict && objectSome(severityCount)) {
177
+ if (strict && data.healthy === false) {
179
178
  process.exit(1)
180
179
  }
181
180
  }
@@ -1,7 +1,14 @@
1
1
  import meow from 'meow'
2
2
 
3
3
  import { printFlagList, printHelpList } from './formatting.js'
4
- import { ensureIsKeyOf } from './type-helpers.js'
4
+
5
+ /**
6
+ * @typedef CliAlias
7
+ * @property {string} description
8
+ * @property {readonly string[]} argv
9
+ */
10
+
11
+ /** @typedef {Record<string, CliAlias>} CliAliases */
5
12
 
6
13
  /**
7
14
  * @callback CliSubcommandRun
@@ -20,39 +27,51 @@ import { ensureIsKeyOf } from './type-helpers.js'
20
27
  /**
21
28
  * @template {import('meow').AnyFlags} Flags
22
29
  * @param {Record<string, CliSubcommand>} subcommands
23
- * @param {import('meow').Options<Flags> & { argv: readonly string[], name: string }} options
30
+ * @param {import('meow').Options<Flags> & { aliases?: CliAliases, argv: readonly string[], name: string }} options
24
31
  * @returns {Promise<void>}
25
32
  */
26
33
  export async function meowWithSubcommands (subcommands, options) {
27
34
  const {
35
+ aliases = {},
28
36
  argv,
29
37
  name,
30
38
  importMeta,
31
39
  ...additionalOptions
32
40
  } = options
33
- const [rawCommandName, ...commandArgv] = argv
34
-
35
- const commandName = ensureIsKeyOf(subcommands, rawCommandName)
36
- const command = commandName ? subcommands[commandName] : undefined
37
-
38
- // If a valid command has been specified, run it...
39
- if (command) {
40
- return await command.run(
41
- commandArgv,
42
- importMeta,
43
- {
44
- parentName: name
45
- }
46
- )
41
+
42
+ const [commandOrAliasName, ...rawCommandArgv] = argv
43
+
44
+ // If we got at least some args, then lets find out if we can find a command
45
+ if (commandOrAliasName) {
46
+ const alias = aliases[commandOrAliasName]
47
+
48
+ // First: Resolve argv data from alias if its an alias that's been given
49
+ const [commandName, ...commandArgv] = alias
50
+ ? [...alias.argv, ...rawCommandArgv]
51
+ : [commandOrAliasName, ...rawCommandArgv]
52
+
53
+ // Second: Find a command definition using that data
54
+ const commandDefinition = commandName ? subcommands[commandName] : undefined
55
+
56
+ // Third: If a valid command has been found, then we run it...
57
+ if (commandDefinition) {
58
+ return await commandDefinition.run(
59
+ commandArgv,
60
+ importMeta,
61
+ {
62
+ parentName: name
63
+ }
64
+ )
65
+ }
47
66
  }
48
67
 
49
- // ...else provide basic instructions and help
68
+ // ...else we provide basic instructions and help
50
69
  const cli = meow(`
51
70
  Usage
52
71
  $ ${name} <command>
53
72
 
54
73
  Commands
55
- ${printHelpList(subcommands, 6)}
74
+ ${printHelpList({ ...subcommands, ...aliases }, 6)}
56
75
 
57
76
  Options
58
77
  ${printFlagList({}, 6)}
@@ -39,6 +39,9 @@ const GLOB_IGNORE = [
39
39
  * @throws {InputError}
40
40
  */
41
41
  export async function getPackageFiles (cwd, inputPaths, config, debugLog) {
42
+ debugLog(`Globbed resolving ${inputPaths.length} paths:`, inputPaths)
43
+
44
+ // TODO: Does not support `~/` paths
42
45
  const entries = await globby(inputPaths, {
43
46
  absolute: true,
44
47
  cwd,
package/lib/utils/sdk.js CHANGED
@@ -1,4 +1,8 @@
1
- import { SocketSdk } from '@socketsecurity/sdk'
1
+ import { readFile } from 'node:fs/promises'
2
+ import { dirname, join } from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+
5
+ import { SocketSdk, createUserAgentFromPkgJson } from '@socketsecurity/sdk'
2
6
  import isInteractive from 'is-interactive'
3
7
  import prompts from 'prompts'
4
8
 
@@ -34,11 +38,14 @@ export async function setupSdk () {
34
38
  https: new HttpsProxyAgent({ proxy: process.env['SOCKET_SECURITY_API_PROXY'] }),
35
39
  }
36
40
  }
41
+ const packageJsonPath = join(dirname(fileURLToPath(import.meta.url)), '../../package.json')
42
+ const packageJson = await readFile(packageJsonPath, 'utf8')
37
43
 
38
44
  /** @type {import('@socketsecurity/sdk').SocketSdkOptions} */
39
45
  const sdkOptions = {
40
46
  agent,
41
47
  baseUrl: process.env['SOCKET_SECURITY_API_BASE_URL'],
48
+ userAgent: createUserAgentFromPkgJson(JSON.parse(packageJson))
42
49
  }
43
50
 
44
51
  return new SocketSdk(apiKey || '', sdkOptions)
@@ -1,13 +1,3 @@
1
- /**
2
- * @template T
3
- * @param {T} obj
4
- * @param {string|undefined} key
5
- * @returns {(keyof T) | undefined}
6
- */
7
- export function ensureIsKeyOf (obj, key) {
8
- return /** @type {keyof T} */ (key && Object.prototype.hasOwnProperty.call(obj, key) ? key : undefined)
9
- }
10
-
11
1
  /**
12
2
  * @param {unknown} value
13
3
  * @returns {value is NodeJS.ErrnoException}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@socketsecurity/cli",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "CLI tool for Socket.dev",
5
5
  "homepage": "http://github.com/SocketDev/socket-cli-js",
6
6
  "repository": {
@@ -47,36 +47,36 @@
47
47
  "@types/node": "^14.18.31",
48
48
  "@types/prompts": "^2.4.1",
49
49
  "@types/update-notifier": "^6.0.1",
50
- "@typescript-eslint/eslint-plugin": "^5.44.0",
51
- "@typescript-eslint/parser": "^5.44.0",
50
+ "@typescript-eslint/eslint-plugin": "^5.48.2",
51
+ "@typescript-eslint/parser": "^5.48.2",
52
52
  "c8": "^7.12.0",
53
53
  "chai": "^4.3.6",
54
54
  "chai-as-promised": "^7.1.1",
55
55
  "dependency-check": "^5.0.0-7",
56
- "eslint": "^8.28.0",
56
+ "eslint": "^8.32.0",
57
57
  "eslint-config-standard": "^17.0.0",
58
58
  "eslint-config-standard-jsx": "^11.0.0",
59
- "eslint-import-resolver-typescript": "^3.5.2",
60
- "eslint-plugin-import": "^2.26.0",
59
+ "eslint-import-resolver-typescript": "^3.5.3",
60
+ "eslint-plugin-import": "^2.27.5",
61
61
  "eslint-plugin-jsdoc": "^39.5.0",
62
- "eslint-plugin-n": "^15.5.1",
62
+ "eslint-plugin-n": "^15.6.1",
63
63
  "eslint-plugin-promise": "^6.1.1",
64
- "eslint-plugin-react": "^7.31.11",
64
+ "eslint-plugin-react": "^7.32.1",
65
65
  "eslint-plugin-react-hooks": "^4.6.0",
66
66
  "eslint-plugin-unicorn": "^45.0.2",
67
67
  "husky": "^8.0.1",
68
68
  "installed-check": "^6.0.5",
69
69
  "mocha": "^10.0.0",
70
70
  "mock-fs": "^5.2.0",
71
- "nock": "^13.2.9",
71
+ "nock": "^13.3.0",
72
72
  "npm-run-all2": "^6.0.2",
73
73
  "type-coverage": "^2.24.1",
74
- "typescript": "~4.9.3"
74
+ "typescript": "~4.9.4"
75
75
  },
76
76
  "dependencies": {
77
77
  "@apideck/better-ajv-errors": "^0.3.6",
78
- "@socketsecurity/config": "^1.2.0",
79
- "@socketsecurity/sdk": "^0.4.0",
78
+ "@socketsecurity/config": "^2.0.0",
79
+ "@socketsecurity/sdk": "^0.5.2",
80
80
  "chalk": "^5.1.2",
81
81
  "globby": "^13.1.3",
82
82
  "hpagent": "^1.2.0",