@socketsecurity/cli 0.9.3 → 0.10.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.
@@ -0,0 +1,115 @@
1
+ /* eslint-disable no-console */
2
+
3
+ import chalk from 'chalk'
4
+ import meow from 'meow'
5
+ import ora from 'ora'
6
+
7
+ import { outputFlags } from '../../flags/index.js'
8
+ import { handleApiCall, handleUnsuccessfulApiResponse } from '../../utils/api-helpers.js'
9
+ import { printFlagList } from '../../utils/formatting.js'
10
+ import { getDefaultKey, setupSdk } from '../../utils/sdk.js'
11
+
12
+ /** @type {import('../../utils/meow-with-subcommands.js').CliSubcommand} */
13
+ export const stream = {
14
+ description: 'Stream the output of a scan',
15
+ async run (argv, importMeta, { parentName }) {
16
+ const name = parentName + ' stream'
17
+
18
+ const input = setupCommand(name, stream.description, argv, importMeta)
19
+ if (input) {
20
+ const spinnerText = 'Streaming scan... \n'
21
+ const spinner = ora(spinnerText).start()
22
+ await getOrgFullScan(input.orgSlug, input.fullScanId, input.file, spinner)
23
+ }
24
+ }
25
+ }
26
+
27
+ // Internal functions
28
+
29
+ /**
30
+ * @typedef CommandContext
31
+ * @property {boolean} outputJson
32
+ * @property {boolean} outputMarkdown
33
+ * @property {string} orgSlug
34
+ * @property {string} fullScanId
35
+ * @property {string | undefined} file
36
+ */
37
+
38
+ /**
39
+ * @param {string} name
40
+ * @param {string} description
41
+ * @param {readonly string[]} argv
42
+ * @param {ImportMeta} importMeta
43
+ * @returns {void|CommandContext}
44
+ */
45
+ function setupCommand (name, description, argv, importMeta) {
46
+ const flags = {
47
+ ...outputFlags
48
+ }
49
+
50
+ const cli = meow(`
51
+ Usage
52
+ $ ${name} <org slug> <scan ID> <path to output file>
53
+
54
+ Options
55
+ ${printFlagList(flags, 6)}
56
+
57
+ Examples
58
+ $ ${name} FakeOrg 000aaaa1-0000-0a0a-00a0-00a0000000a0 ./stream.txt
59
+ `, {
60
+ argv,
61
+ description,
62
+ importMeta,
63
+ flags
64
+ })
65
+
66
+ const {
67
+ json: outputJson,
68
+ markdown: outputMarkdown,
69
+ } = cli.flags
70
+
71
+ if (cli.input.length < 2) {
72
+ console.error(`${chalk.bgRed('Input error')}: Please specify an organization slug and a scan ID.\n`)
73
+ cli.showHelp()
74
+ return
75
+ }
76
+
77
+ const [orgSlug = '', fullScanId = '', file] = cli.input
78
+
79
+ return {
80
+ outputJson,
81
+ outputMarkdown,
82
+ orgSlug,
83
+ fullScanId,
84
+ file
85
+ }
86
+ }
87
+
88
+ /**
89
+ * @typedef FullScanData
90
+ * @property {import('@socketsecurity/sdk').SocketSdkReturnType<'getOrgFullScan'>["data"]} data
91
+ */
92
+
93
+ /**
94
+ * @param {string} orgSlug
95
+ * @param {string} fullScanId
96
+ * @param {string | undefined} file
97
+ * @param {import('ora').Ora} spinner
98
+ * @returns {Promise<void|FullScanData>}
99
+ */
100
+ async function getOrgFullScan (orgSlug, fullScanId, file, spinner) {
101
+ const socketSdk = await setupSdk(getDefaultKey())
102
+ const result = await handleApiCall(socketSdk.getOrgFullScan(orgSlug, fullScanId, file), 'Streaming a scan')
103
+
104
+ if (!result?.success) {
105
+ return handleUnsuccessfulApiResponse('getOrgFullScan', result, spinner)
106
+ }
107
+
108
+ spinner.stop()
109
+
110
+ console.log(file ? `\nFull scan details written to ${file} \n` : '\nFull scan details: \n')
111
+
112
+ return {
113
+ data: result.data
114
+ }
115
+ }
@@ -11,7 +11,7 @@ import { printFlagList } from '../../utils/formatting.js'
11
11
  const BASH_FILE = `${homedir.homedir()}/.bashrc`
12
12
  const ZSH_BASH_FILE = `${homedir.homedir()}/.zshrc`
13
13
 
14
- /** @type {import('../../utils/meow-with-subcommands').CliSubcommand} */
14
+ /** @type {import('../../utils/meow-with-subcommands.js').CliSubcommand} */
15
15
  export const wrapper = {
16
16
  description: 'Enable or disable the Socket npm/npx wrapper',
17
17
  async run (argv, importMeta, { parentName }) {
@@ -254,7 +254,7 @@ const ttyServerPromise = chalkPromise.then(async (chalk) => {
254
254
  const npmEntrypoint = fs.realpathSync(`${process.argv[1]}`)
255
255
  /**
256
256
  * @param {string} filepath
257
- * @returns {string}
257
+ * @returns {string | null}
258
258
  */
259
259
  function findRoot (filepath) {
260
260
  if (path.basename(filepath) === 'npm') {
@@ -262,13 +262,38 @@ function findRoot (filepath) {
262
262
  }
263
263
  const parent = path.dirname(filepath)
264
264
  if (parent === filepath) {
265
- process.exit(127)
265
+ return null
266
266
  }
267
267
  return findRoot(parent)
268
268
  }
269
269
  const npmDir = findRoot(path.dirname(npmEntrypoint))
270
- const arboristLibClassPath = path.join(npmDir, 'node_modules', '@npmcli', 'arborist', 'lib', 'arborist', 'index.js')
271
- const npmlog = require(path.join(npmDir, 'node_modules', 'npmlog', 'lib', 'log.js'))
270
+ if (npmDir === null) {
271
+ console.error('Unable to find npm cli install directory, this is potentiall a bug with socket-npm caused by changes to npm cli.')
272
+ console.error(`Searched parent directories of ${npmEntrypoint}`)
273
+ process.exit(127)
274
+ }
275
+ let arboristLibClassPath
276
+ try {
277
+ arboristLibClassPath = path.join(npmDir, 'node_modules', '@npmcli', 'arborist', 'lib', 'arborist', 'index.js')
278
+ } catch (e) {
279
+ console.error('Unable to integrate with npm cli internals, this is potentially a bug with socket-npm caused by changes to npm cli.')
280
+ process.exit(127);
281
+ }
282
+
283
+ let npmlog
284
+
285
+ try {
286
+ npmlog = require(path.join(npmDir, 'node_modules', 'npmlog', 'lib', 'log.js'))
287
+ } catch {
288
+ try {
289
+ const { log } = require(path.join(npmDir, 'node_modules', 'proc-log', 'lib', 'index.js'))
290
+ npmlog = log
291
+ } catch {
292
+ console.error('Unable to integrate with npm cli logging infrastructure, this is potentially a bug with socket-npm caused by changes to npm cli.')
293
+ process.exit(127);
294
+ }
295
+ }
296
+
272
297
  /**
273
298
  * @type {import('pacote')}
274
299
  */
@@ -27,7 +27,7 @@ const SEVERITIES_BY_ORDER = /** @type {const} */ ([
27
27
 
28
28
  return result
29
29
  }
30
-
30
+ /* TODO: Delete this function when we remove the report command */
31
31
  /**
32
32
  * @param {SocketIssueList} issues
33
33
  * @param {SocketIssue['severity']} [lowestToInclude]
@@ -54,6 +54,33 @@ export function getSeverityCount (issues, lowestToInclude) {
54
54
  return severityCount
55
55
  }
56
56
 
57
+ /* The following function is the updated one */
58
+ /**
59
+ * @param {Array<SocketIssue>} issues
60
+ * @param {SocketIssue['severity']} [lowestToInclude]
61
+ * @returns {Record<SocketIssue['severity'], number>}
62
+ */
63
+ export function getCountSeverity (issues, lowestToInclude) {
64
+ const severityCount = pick(
65
+ { low: 0, middle: 0, high: 0, critical: 0 },
66
+ getDesiredSeverities(lowestToInclude)
67
+ )
68
+
69
+ for (const issue of issues) {
70
+ const severity = issue.severity
71
+
72
+ if (!severity) {
73
+ continue
74
+ }
75
+
76
+ if (severityCount[severity] !== undefined) {
77
+ severityCount[severity] += 1
78
+ }
79
+ }
80
+
81
+ return severityCount
82
+ }
83
+
57
84
  /**
58
85
  * @param {Record<SocketIssue['severity'], number>} severityCount
59
86
  * @returns {string}
@@ -25,9 +25,8 @@ import { printFlagList, printHelpList } from './formatting.js'
25
25
  */
26
26
 
27
27
  /**
28
- * @template {import('meow').AnyFlags} Flags
29
28
  * @param {Record<string, CliSubcommand>} subcommands
30
- * @param {import('meow').Options<Flags> & { aliases?: CliAliases, argv: readonly string[], name: string }} options
29
+ * @param {import('meow').Options<any> & { aliases?: CliAliases, argv: readonly string[], name: string }} options
31
30
  * @returns {Promise<void>}
32
31
  */
33
32
  export async function meowWithSubcommands (subcommands, options) {
package/lib/utils/misc.js CHANGED
@@ -30,7 +30,6 @@ export function stringJoinWithSeparateFinalSeparator (list, separator = ' and ')
30
30
 
31
31
  /**
32
32
  * Returns a new object with only the specified keys from the input object
33
- *
34
33
  * @template {Record<string,any>} T
35
34
  * @template {keyof T} K
36
35
  * @param {T} input
@@ -12,7 +12,6 @@ import { isErrnoException } from './type-helpers.cjs'
12
12
 
13
13
  /**
14
14
  * There are a lot of possible folders that we should not be looking in and "ignore-by-default" helps us with defining those
15
- *
16
15
  * @type {readonly string[]}
17
16
  */
18
17
  const ignoreByDefault = directories()
@@ -31,7 +30,6 @@ const BASE_GLOBBY_OPTS = {
31
30
 
32
31
  /**
33
32
  * Resolves package.json and lockfiles from (globbed) input paths, applying relevant ignores
34
- *
35
33
  * @param {string} cwd The working directory to use when resolving paths
36
34
  * @param {string[]} inputPaths A list of paths to folders, package.json files and/or recognized lockfiles. Supports globs.
37
35
  * @param {import('@socketsecurity/config').SocketYml|undefined} config
@@ -57,18 +55,46 @@ export async function getPackageFiles (cwd, inputPaths, config, supportedFiles,
57
55
  debugLog(`Mapped ${entries.length} entries to ${packageFiles.length} files:`, packageFiles)
58
56
 
59
57
  const includedPackageFiles = config?.projectIgnorePaths?.length
58
+ // @ts-ignore
60
59
  ? ignore()
61
60
  .add(config.projectIgnorePaths)
62
61
  .filter(packageFiles.map(item => path.relative(cwd, item)))
63
- .map(item => path.resolve(cwd, item))
62
+ .map((/** @type {string} */ item) => path.resolve(cwd, item))
64
63
  : packageFiles
65
64
 
66
65
  return includedPackageFiles
67
66
  }
68
67
 
68
+ /**
69
+ * Resolves package.json and lockfiles from (globbed) input paths, applying relevant ignores
70
+ * @param {string} cwd The working directory to use when resolving paths
71
+ * @param {string[]} inputPaths A list of paths to folders, package.json files and/or recognized lockfiles. Supports globs.
72
+ * @param {import('@socketsecurity/sdk').SocketSdkReturnType<"getReportSupportedFiles">['data']} supportedFiles
73
+ * @param {typeof console.error} debugLog
74
+ * @returns {Promise<string[]>}
75
+ * @throws {InputError}
76
+ */
77
+ export async function getPackageFilesFullScans (cwd, inputPaths, supportedFiles, debugLog) {
78
+ debugLog(`Globbed resolving ${inputPaths.length} paths:`, inputPaths)
79
+
80
+ // TODO: Does not support `~/` paths
81
+ const entries = await globby(inputPaths, {
82
+ ...BASE_GLOBBY_OPTS,
83
+ cwd,
84
+ onlyFiles: false
85
+ })
86
+
87
+ debugLog(`Globbed resolved ${inputPaths.length} paths to ${entries.length} paths:`, entries)
88
+
89
+ const packageFiles = await mapGlobResultToFiles(entries, supportedFiles)
90
+
91
+ debugLog(`Mapped ${entries.length} entries to ${packageFiles.length} files:`, packageFiles)
92
+
93
+ return packageFiles
94
+ }
95
+
69
96
  /**
70
97
  * Takes paths to folders, package.json and/or recognized lock files and resolves them to package.json + lockfile pairs (where possible)
71
- *
72
98
  * @param {string[]} entries
73
99
  * @param {import('@socketsecurity/sdk').SocketSdkReturnType<"getReportSupportedFiles">['data']} supportedFiles
74
100
  * @returns {Promise<string[]>}
@@ -86,7 +112,6 @@ export async function mapGlobResultToFiles (entries, supportedFiles) {
86
112
 
87
113
  /**
88
114
  * Takes a single path to a folder, package.json or a recognized lock file and resolves to a package.json + lockfile pair (where possible)
89
- *
90
115
  * @param {string} entry
91
116
  * @param {import('@socketsecurity/sdk').SocketSdkReturnType<'getReportSupportedFiles'>['data']} supportedFiles
92
117
  * @returns {Promise<string[]>}
package/lib/utils/sdk.js CHANGED
@@ -13,7 +13,6 @@ export const FREE_API_KEY = 'sktsec_t_--RAN5U4ivauy4w37-6aoKyYPDt5ZbaT5JBVMqiwKo
13
13
 
14
14
  /**
15
15
  * This API key should be stored globally for the duration of the CLI execution
16
- *
17
16
  * @type {string | undefined}
18
17
  */
19
18
  let defaultKey
@@ -26,7 +25,6 @@ export function getDefaultKey () {
26
25
 
27
26
  /**
28
27
  * The API server that should be used for operations
29
- *
30
28
  * @type {string | undefined}
31
29
  */
32
30
  let defaultAPIBaseUrl
@@ -41,7 +39,6 @@ export function getDefaultAPIBaseUrl () {
41
39
 
42
40
  /**
43
41
  * The API server that should be used for operations
44
- *
45
42
  * @type {string | undefined}
46
43
  */
47
44
  let defaultApiProxy
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@socketsecurity/cli",
3
- "version": "0.9.3",
3
+ "version": "0.10.1",
4
4
  "description": "CLI tool for Socket.dev",
5
5
  "homepage": "http://github.com/SocketDev/socket-cli-js",
6
6
  "repository": {
@@ -14,9 +14,6 @@
14
14
  "url": "https://socket.dev"
15
15
  },
16
16
  "license": "MIT",
17
- "engines": {
18
- "node": "^16.13.0 || >=18.0.0"
19
- },
20
17
  "type": "module",
21
18
  "bin": {
22
19
  "socket": "cli.js",
@@ -30,73 +27,91 @@
30
27
  "lib/**/*.cjs",
31
28
  "lib/shadow/**"
32
29
  ],
30
+ "dependencies": {
31
+ "@apideck/better-ajv-errors": "^0.3.6",
32
+ "@cyclonedx/cdxgen": "^10.7.0",
33
+ "@inquirer/select": "^2.3.5",
34
+ "@socketsecurity/config": "^2.1.3",
35
+ "@socketsecurity/sdk": "^1.2.0",
36
+ "chalk": "^5.3.0",
37
+ "chalk-table": "^1.0.2",
38
+ "execa": "^9.1.0",
39
+ "globby": "^14.0.1",
40
+ "hpagent": "^1.2.0",
41
+ "ignore": "^5.3.1",
42
+ "ignore-by-default": "^2.1.0",
43
+ "inquirer": "^9.2.23",
44
+ "is-interactive": "^2.0.0",
45
+ "is-unicode-supported": "^2.0.0",
46
+ "meow": "^13.2.0",
47
+ "open": "^10.1.0",
48
+ "ora": "^8.0.1",
49
+ "pony-cause": "^2.1.11",
50
+ "prompts": "^2.4.2",
51
+ "synp": "^1.9.13",
52
+ "terminal-link": "^3.0.0",
53
+ "update-notifier": "^7.0.0",
54
+ "which": "^4.0.0",
55
+ "yargs-parser": "^21.1.1"
56
+ },
57
+ "devDependencies": {
58
+ "@socketsecurity/eslint-config": "^5.0.1",
59
+ "@tsconfig/node20": "^20.1.4",
60
+ "@types/chai": "^4.3.16",
61
+ "@types/chai-as-promised": "^7.1.8",
62
+ "@types/inquirer": "^9.0.7",
63
+ "@types/micromatch": "^4.0.7",
64
+ "@types/mocha": "^10.0.6",
65
+ "@types/mock-fs": "^4.13.4",
66
+ "@types/node": "^20.12.13",
67
+ "@types/npm": "^7.19.3",
68
+ "@types/npmcli__arborist": "^5.6.6",
69
+ "@types/prompts": "^2.4.9",
70
+ "@types/update-notifier": "^6.0.8",
71
+ "@types/which": "^3.0.4",
72
+ "@types/yargs-parser": "^21.0.3",
73
+ "@typescript-eslint/eslint-plugin": "^7.11.0",
74
+ "@typescript-eslint/parser": "7.10.0",
75
+ "c8": "^10.1.2",
76
+ "dependency-check": "^5.0.0-7",
77
+ "eslint": "^8.56.0",
78
+ "eslint-config-standard": "^17.1.0",
79
+ "eslint-config-standard-jsx": "^11.0.0",
80
+ "eslint-import-resolver-typescript": "^3.6.1",
81
+ "eslint-plugin-import": "^2.29.1",
82
+ "eslint-plugin-jsdoc": "^48.2.7",
83
+ "eslint-plugin-n": "^16.6.2",
84
+ "eslint-plugin-promise": "^6.2.0",
85
+ "eslint-plugin-react": "^7.34.2",
86
+ "eslint-plugin-react-hooks": "^4.6.2",
87
+ "eslint-plugin-unicorn": "^48.0.1",
88
+ "husky": "^9.0.11",
89
+ "installed-check": "^9.3.0",
90
+ "mock-fs": "^5.2.0",
91
+ "nock": "^13.5.4",
92
+ "npm-run-all2": "^6.2.0",
93
+ "type-coverage": "^2.29.0",
94
+ "typescript": "~5.5.2"
95
+ },
96
+ "overrides": {
97
+ "@cyclonedx/cdxgen": {
98
+ "packageurl-js": "https://registry.npmjs.org/@jdalton/packageurl-js/-/packageurl-js-1.2.7.tgz"
99
+ }
100
+ },
101
+ "engines": {
102
+ "node": "^20.9.0 || >=21.1.0"
103
+ },
33
104
  "scripts": {
34
- "check:dependency-check": "dependency-check '*.js' 'lib/shadow/*.cjs' '*.mjs' 'test/**/*.js' --no-dev --ignore-module node:test --ignore-module node:assert/strict",
105
+ "check:dependency-check": "dependency-check '*.js' 'lib/shadow/*.cjs' '*.mjs' 'test/*.js' --no-dev --ignore-module node:* --ignore-module @cyclonedx/* --ignore-module synp",
35
106
  "check:installed-check": "installed-check -i eslint-plugin-jsdoc",
36
107
  "check:lint": "eslint --report-unused-disable-directives .",
37
108
  "check:tsc": "tsc",
38
109
  "check:type-coverage": "type-coverage --detail --strict --at-least 95 --ignore-files 'test/*'",
39
110
  "check": "run-p -c --aggregate-output check:*",
40
- "prepare": "husky install",
111
+ "prepare": "husky",
41
112
  "test:unit": "c8 --reporter=lcov --reporter text node --test",
42
113
  "test-ci": "run-s test:*",
43
114
  "test": "run-s check test:*",
44
115
  "//postinstall": "node ./cli.js wrapper --postinstall"
45
- },
46
- "devDependencies": {
47
- "@socketsecurity/eslint-config": "^3.0.1",
48
- "@tsconfig/node14": "^14.1.0",
49
- "@types/chai": "^4.3.3",
50
- "@types/chai-as-promised": "^7.1.5",
51
- "@types/micromatch": "^4.0.2",
52
- "@types/mocha": "^10.0.1",
53
- "@types/mock-fs": "^4.13.1",
54
- "@types/node": "^20.4.2",
55
- "@types/npm": "^7.19.0",
56
- "@types/npmcli__arborist": "^5.6.1",
57
- "@types/prompts": "^2.4.1",
58
- "@types/update-notifier": "^6.0.2",
59
- "@types/which": "^3.0.0",
60
- "@typescript-eslint/eslint-plugin": "^5.51.0",
61
- "@typescript-eslint/parser": "^5.51.0",
62
- "c8": "^8.0.0",
63
- "dependency-check": "^5.0.0-7",
64
- "eslint": "^8.34.0",
65
- "eslint-config-standard": "^17.0.0",
66
- "eslint-config-standard-jsx": "^11.0.0",
67
- "eslint-import-resolver-typescript": "^3.5.3",
68
- "eslint-plugin-import": "^2.27.5",
69
- "eslint-plugin-jsdoc": "^40.0.0",
70
- "eslint-plugin-n": "^15.6.1",
71
- "eslint-plugin-promise": "^6.1.1",
72
- "eslint-plugin-react": "^7.32.2",
73
- "eslint-plugin-react-hooks": "^4.6.0",
74
- "eslint-plugin-unicorn": "^45.0.2",
75
- "husky": "^8.0.1",
76
- "installed-check": "^6.0.5",
77
- "mock-fs": "^5.2.0",
78
- "nock": "^13.3.0",
79
- "npm-run-all2": "^6.0.2",
80
- "type-coverage": "^2.24.1",
81
- "typescript": "~5.1.6"
82
- },
83
- "dependencies": {
84
- "@apideck/better-ajv-errors": "^0.3.6",
85
- "@socketsecurity/config": "^2.0.0",
86
- "@socketsecurity/sdk": "^0.7.3",
87
- "chalk": "^5.1.2",
88
- "globby": "^13.1.3",
89
- "hpagent": "^1.2.0",
90
- "ignore": "^5.2.1",
91
- "ignore-by-default": "^2.1.0",
92
- "is-interactive": "^2.0.0",
93
- "is-unicode-supported": "^1.3.0",
94
- "meow": "^12.0.1",
95
- "ora": "^6.1.2",
96
- "pony-cause": "^2.1.8",
97
- "prompts": "^2.4.2",
98
- "terminal-link": "^3.0.0",
99
- "update-notifier": "^6.0.2",
100
- "which": "^3.0.0"
101
116
  }
102
117
  }