@socketsecurity/cli 0.10.1 → 0.11.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/README.md +22 -22
- package/bin/npm +2 -0
- package/bin/npx +2 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +3419 -0
- package/dist/errors.d.ts +7 -0
- package/dist/link.d.ts +2 -0
- package/dist/link.js +45 -0
- package/dist/npm-cli.d.ts +2 -0
- package/dist/npm-cli.js +84 -0
- package/dist/npm-injection.d.ts +1 -0
- package/dist/npm-injection.js +913 -0
- package/dist/npm-injection2.d.ts +25 -0
- package/dist/npm-injection2.js +899 -0
- package/dist/npx-cli.d.ts +2 -0
- package/dist/npx-cli.js +60 -0
- package/dist/path-resolve.d.ts +12 -0
- package/dist/path-resolve.js +139 -0
- package/dist/sdk.d.ts +27 -0
- package/dist/sdk.js +224 -0
- package/dist/settings.d.ts +9 -0
- package/dist/type-helpers.d.ts +3 -0
- package/dist/vendor.js +25421 -0
- package/package.json +105 -52
- package/{lib/shadow/translations.json → translations.json} +20 -20
- package/cli.js +0 -72
- package/lib/commands/audit-log/index.js +0 -162
- package/lib/commands/cdxgen/index.js +0 -211
- package/lib/commands/dependencies/index.js +0 -150
- package/lib/commands/index.js +0 -14
- package/lib/commands/info/index.js +0 -287
- package/lib/commands/login/index.js +0 -170
- package/lib/commands/logout/index.js +0 -35
- package/lib/commands/npm/index.js +0 -27
- package/lib/commands/npx/index.js +0 -22
- package/lib/commands/raw-npm/index.js +0 -59
- package/lib/commands/raw-npx/index.js +0 -59
- package/lib/commands/report/create.js +0 -251
- package/lib/commands/report/index.js +0 -24
- package/lib/commands/report/view.js +0 -176
- package/lib/commands/repos/create.js +0 -166
- package/lib/commands/repos/delete.js +0 -93
- package/lib/commands/repos/index.js +0 -30
- package/lib/commands/repos/list.js +0 -170
- package/lib/commands/repos/update.js +0 -166
- package/lib/commands/repos/view.js +0 -128
- package/lib/commands/scan/create.js +0 -245
- package/lib/commands/scan/delete.js +0 -112
- package/lib/commands/scan/index.js +0 -30
- package/lib/commands/scan/list.js +0 -192
- package/lib/commands/scan/metadata.js +0 -113
- package/lib/commands/scan/stream.js +0 -115
- package/lib/commands/wrapper/index.js +0 -199
- package/lib/flags/command.js +0 -14
- package/lib/flags/index.js +0 -3
- package/lib/flags/output.js +0 -16
- package/lib/flags/validation.js +0 -14
- package/lib/shadow/bin/npm +0 -2
- package/lib/shadow/bin/npx +0 -2
- package/lib/shadow/link.cjs +0 -50
- package/lib/shadow/npm-cli.cjs +0 -27
- package/lib/shadow/npm-injection.cjs +0 -649
- package/lib/shadow/npx-cli.cjs +0 -27
- package/lib/shadow/package.json +0 -3
- package/lib/shadow/tty-server.cjs +0 -222
- package/lib/shadow/update-notifier.mjs +0 -3
- package/lib/utils/api-helpers.js +0 -42
- package/lib/utils/chalk-markdown.js +0 -125
- package/lib/utils/errors.js +0 -14
- package/lib/utils/flags.js +0 -27
- package/lib/utils/format-issues.js +0 -99
- package/lib/utils/formatting.js +0 -47
- package/lib/utils/issue-rules.cjs +0 -180
- package/lib/utils/meow-with-subcommands.js +0 -87
- package/lib/utils/misc.js +0 -61
- package/lib/utils/path-resolve.js +0 -204
- package/lib/utils/sdk.js +0 -99
- package/lib/utils/settings.js +0 -69
- package/lib/utils/type-helpers.cjs +0 -13
- package/lib/utils/update-notifier.js +0 -18
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-console */
|
|
2
|
-
|
|
3
|
-
import { existsSync, promises as fs } from 'node:fs'
|
|
4
|
-
import path from 'node:path'
|
|
5
|
-
import { fileURLToPath } from 'node:url'
|
|
6
|
-
|
|
7
|
-
import chalk from 'chalk'
|
|
8
|
-
import { $ } from 'execa'
|
|
9
|
-
import yargsParse from 'yargs-parser'
|
|
10
|
-
|
|
11
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
12
|
-
|
|
13
|
-
const {
|
|
14
|
-
SBOM_SIGN_ALGORITHM, // Algorithm. Example: RS512
|
|
15
|
-
SBOM_SIGN_PRIVATE_KEY, // Location to the RSA private key
|
|
16
|
-
SBOM_SIGN_PUBLIC_KEY // Optional. Location to the RSA public key
|
|
17
|
-
} = process.env
|
|
18
|
-
|
|
19
|
-
const toLower = (/** @type {string} */ arg) => arg.toLowerCase()
|
|
20
|
-
const arrayToLower = (/** @type {string[]} */ arg) => arg.map(toLower)
|
|
21
|
-
|
|
22
|
-
const execaConfig = {
|
|
23
|
-
env: { NODE_ENV: '' },
|
|
24
|
-
localDir: path.join(__dirname, 'node_modules'),
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const nodejsPlatformTypes = [
|
|
28
|
-
'javascript',
|
|
29
|
-
'js',
|
|
30
|
-
'nodejs',
|
|
31
|
-
'npm',
|
|
32
|
-
'pnpm',
|
|
33
|
-
'ts',
|
|
34
|
-
'tsx',
|
|
35
|
-
'typescript'
|
|
36
|
-
]
|
|
37
|
-
|
|
38
|
-
const yargsConfig = {
|
|
39
|
-
configuration: {
|
|
40
|
-
'camel-case-expansion': false,
|
|
41
|
-
'strip-aliased': true,
|
|
42
|
-
'parse-numbers': false,
|
|
43
|
-
'populate--': true,
|
|
44
|
-
'unknown-options-as-args': true
|
|
45
|
-
},
|
|
46
|
-
coerce: {
|
|
47
|
-
author: arrayToLower,
|
|
48
|
-
filter: arrayToLower,
|
|
49
|
-
only: arrayToLower,
|
|
50
|
-
profile: toLower,
|
|
51
|
-
standard: arrayToLower,
|
|
52
|
-
type: toLower
|
|
53
|
-
},
|
|
54
|
-
default: {
|
|
55
|
-
//author: ['OWASP Foundation'],
|
|
56
|
-
//'auto-compositions': true,
|
|
57
|
-
//babel: true,
|
|
58
|
-
//evidence: false,
|
|
59
|
-
//'include-crypto': false,
|
|
60
|
-
//'include-formulation': false,
|
|
61
|
-
//'install-deps': true,
|
|
62
|
-
//output: 'bom.json',
|
|
63
|
-
//profile: 'generic',
|
|
64
|
-
//'project-version': '',
|
|
65
|
-
//recurse: true,
|
|
66
|
-
//'server-host': '127.0.0.1',
|
|
67
|
-
//'server-port': '9090',
|
|
68
|
-
//'spec-version': '1.5',
|
|
69
|
-
type: 'js',
|
|
70
|
-
//validate: true,
|
|
71
|
-
},
|
|
72
|
-
alias: {
|
|
73
|
-
help: ['h'],
|
|
74
|
-
output: ['o'],
|
|
75
|
-
print: ['p'],
|
|
76
|
-
recurse: ['r'],
|
|
77
|
-
'resolve-class': ['c'],
|
|
78
|
-
type: ['t'],
|
|
79
|
-
version: ['v'],
|
|
80
|
-
},
|
|
81
|
-
array: [
|
|
82
|
-
{ key: 'author', type: 'string' },
|
|
83
|
-
{ key: 'exclude', type: 'string' },
|
|
84
|
-
{ key: 'filter', type: 'string' },
|
|
85
|
-
{ key: 'only', type: 'string' },
|
|
86
|
-
{ key: 'standard', type: 'string' }
|
|
87
|
-
],
|
|
88
|
-
boolean: [
|
|
89
|
-
'auto-compositions',
|
|
90
|
-
'babel',
|
|
91
|
-
'deep',
|
|
92
|
-
'evidence',
|
|
93
|
-
'fail-on-error',
|
|
94
|
-
'generate-key-and-sign',
|
|
95
|
-
'help',
|
|
96
|
-
'include-formulation',
|
|
97
|
-
'include-crypto',
|
|
98
|
-
'install-deps',
|
|
99
|
-
'print',
|
|
100
|
-
'required-only',
|
|
101
|
-
'server',
|
|
102
|
-
'validate',
|
|
103
|
-
'version',
|
|
104
|
-
],
|
|
105
|
-
string: [
|
|
106
|
-
'api-key',
|
|
107
|
-
'output',
|
|
108
|
-
'parent-project-id',
|
|
109
|
-
'profile',
|
|
110
|
-
'project-group',
|
|
111
|
-
'project-name',
|
|
112
|
-
'project-version',
|
|
113
|
-
'project-id',
|
|
114
|
-
'server-host',
|
|
115
|
-
'server-port',
|
|
116
|
-
'server-url',
|
|
117
|
-
'spec-version',
|
|
118
|
-
]
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
*
|
|
123
|
-
* @param {{ [key: string]: boolean | null | number | string | (string | number)[]}} argv
|
|
124
|
-
* @returns {string[]}
|
|
125
|
-
*/
|
|
126
|
-
function argvToArray (/** @type {any} */ argv) {
|
|
127
|
-
if (argv['help']) return ['--help']
|
|
128
|
-
const result = []
|
|
129
|
-
for (const { 0: key, 1: value } of Object.entries(argv)) {
|
|
130
|
-
if (key === '_' || key === '--') continue
|
|
131
|
-
if (key === 'babel' || key === 'install-deps' || key === 'validate') {
|
|
132
|
-
// cdxgen documents no-babel, no-install-deps, and no-validate flags so
|
|
133
|
-
// use them when relevant.
|
|
134
|
-
result.push(`--${value ? key : `no-${key}`}`)
|
|
135
|
-
} else if (value === true) {
|
|
136
|
-
result.push(`--${key}`)
|
|
137
|
-
} else if (typeof value === 'string') {
|
|
138
|
-
result.push(`--${key}=${value}`)
|
|
139
|
-
} else if (Array.isArray(value)) {
|
|
140
|
-
result.push(`--${key}`, ...value.map(String))
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
if (argv['--']) {
|
|
144
|
-
result.push('--', ...argv['--'])
|
|
145
|
-
}
|
|
146
|
-
return result
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/** @type {import('../../utils/meow-with-subcommands.js').CliSubcommand} */
|
|
150
|
-
export const cdxgen = {
|
|
151
|
-
description: 'Create an SBOM with CycloneDX generator (cdxgen)',
|
|
152
|
-
async run (argv_) {
|
|
153
|
-
const /** @type {any} */ yargv = {
|
|
154
|
-
__proto__: null,
|
|
155
|
-
// @ts-ignore
|
|
156
|
-
...yargsParse(argv_, yargsConfig)
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const /** @type {string[]} */ unknown = yargv._
|
|
160
|
-
const { length: unknownLength } = unknown
|
|
161
|
-
if (unknownLength) {
|
|
162
|
-
console.error(`Unknown argument${unknownLength > 1 ? 's' : ''}: ${yargv._.join(', ')}`)
|
|
163
|
-
process.exitCode = 1
|
|
164
|
-
return
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
let cleanupPackageLock = false
|
|
168
|
-
if (
|
|
169
|
-
yargv.type !== 'yarn' &&
|
|
170
|
-
nodejsPlatformTypes.includes(yargv.type) &&
|
|
171
|
-
existsSync('./yarn.lock')
|
|
172
|
-
) {
|
|
173
|
-
if (existsSync('./package-lock.json')) {
|
|
174
|
-
yargv.type = 'npm'
|
|
175
|
-
} else {
|
|
176
|
-
// Use synp to create a package-lock.json from the yarn.lock,
|
|
177
|
-
// based on the node_modules folder, for a more accurate SBOM.
|
|
178
|
-
try {
|
|
179
|
-
await $(execaConfig)`synp --source-file ./yarn.lock`
|
|
180
|
-
yargv.type = 'npm'
|
|
181
|
-
cleanupPackageLock = true
|
|
182
|
-
} catch {}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (yargv.output === undefined) {
|
|
187
|
-
yargv.output = 'socket-cdx.json'
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
await $({
|
|
191
|
-
...execaConfig,
|
|
192
|
-
env: {
|
|
193
|
-
NODE_ENV: '',
|
|
194
|
-
SBOM_SIGN_ALGORITHM,
|
|
195
|
-
SBOM_SIGN_PRIVATE_KEY,
|
|
196
|
-
SBOM_SIGN_PUBLIC_KEY
|
|
197
|
-
},
|
|
198
|
-
stdout: 'inherit'
|
|
199
|
-
})`cdxgen ${argvToArray(yargv)}`
|
|
200
|
-
|
|
201
|
-
if (cleanupPackageLock) {
|
|
202
|
-
try {
|
|
203
|
-
await fs.unlink('./package-lock.json')
|
|
204
|
-
} catch {}
|
|
205
|
-
}
|
|
206
|
-
const fullOutputPath = path.join(process.cwd(), yargv.output)
|
|
207
|
-
if (existsSync(fullOutputPath)) {
|
|
208
|
-
console.log(chalk.cyanBright(`${yargv.output} created!`))
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-console */
|
|
2
|
-
|
|
3
|
-
import chalk from 'chalk'
|
|
4
|
-
// @ts-ignore
|
|
5
|
-
import chalkTable from 'chalk-table'
|
|
6
|
-
import meow from 'meow'
|
|
7
|
-
import ora from 'ora'
|
|
8
|
-
|
|
9
|
-
import { outputFlags } from '../../flags/index.js'
|
|
10
|
-
import { handleApiCall, handleUnsuccessfulApiResponse } from '../../utils/api-helpers.js'
|
|
11
|
-
import { prepareFlags } from '../../utils/flags.js'
|
|
12
|
-
import { printFlagList } from '../../utils/formatting.js'
|
|
13
|
-
import { getDefaultKey, setupSdk } from '../../utils/sdk.js'
|
|
14
|
-
|
|
15
|
-
/** @type {import('../../utils/meow-with-subcommands.js').CliSubcommand} */
|
|
16
|
-
export const dependencies = {
|
|
17
|
-
description: 'Search for any dependency that is being used in your organization',
|
|
18
|
-
async run (argv, importMeta, { parentName }) {
|
|
19
|
-
const name = parentName + ' dependencies'
|
|
20
|
-
|
|
21
|
-
const input = setupCommand(name, dependencies.description, argv, importMeta)
|
|
22
|
-
if (input) {
|
|
23
|
-
const spinnerText = 'Searching dependencies...'
|
|
24
|
-
const spinner = ora(spinnerText).start()
|
|
25
|
-
await searchDeps(input, spinner)
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const dependenciesFlags = prepareFlags({
|
|
31
|
-
limit: {
|
|
32
|
-
type: 'number',
|
|
33
|
-
shortFlag: 'l',
|
|
34
|
-
default: 50,
|
|
35
|
-
description: 'Maximum number of dependencies returned',
|
|
36
|
-
},
|
|
37
|
-
offset: {
|
|
38
|
-
type: 'number',
|
|
39
|
-
shortFlag: 'o',
|
|
40
|
-
default: 0,
|
|
41
|
-
description: 'Page number',
|
|
42
|
-
}
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
// Internal functions
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* @typedef Command
|
|
49
|
-
* @property {boolean} outputJson
|
|
50
|
-
* @property {boolean} outputMarkdown
|
|
51
|
-
* @property {number} limit
|
|
52
|
-
* @property {number} offset
|
|
53
|
-
*/
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* @param {string} name
|
|
57
|
-
* @param {string} description
|
|
58
|
-
* @param {readonly string[]} argv
|
|
59
|
-
* @param {ImportMeta} importMeta
|
|
60
|
-
* @returns {void|Command}
|
|
61
|
-
*/
|
|
62
|
-
function setupCommand (name, description, argv, importMeta) {
|
|
63
|
-
const flags = {
|
|
64
|
-
...outputFlags,
|
|
65
|
-
...dependenciesFlags
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const cli = meow(`
|
|
69
|
-
Usage
|
|
70
|
-
$ ${name}
|
|
71
|
-
|
|
72
|
-
Options
|
|
73
|
-
${printFlagList(flags, 6)}
|
|
74
|
-
|
|
75
|
-
Examples
|
|
76
|
-
$ ${name}
|
|
77
|
-
`, {
|
|
78
|
-
argv,
|
|
79
|
-
description,
|
|
80
|
-
importMeta,
|
|
81
|
-
flags
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
const {
|
|
85
|
-
json: outputJson,
|
|
86
|
-
markdown: outputMarkdown,
|
|
87
|
-
limit,
|
|
88
|
-
offset
|
|
89
|
-
} = cli.flags
|
|
90
|
-
|
|
91
|
-
return {
|
|
92
|
-
outputJson,
|
|
93
|
-
outputMarkdown,
|
|
94
|
-
limit,
|
|
95
|
-
offset
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* @typedef DependenciesData
|
|
101
|
-
* @property {import('@socketsecurity/sdk').SocketSdkReturnType<'searchDependencies'>["data"]} data
|
|
102
|
-
*/
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* @param {Command} input
|
|
106
|
-
* @param {import('ora').Ora} spinner
|
|
107
|
-
* @returns {Promise<void|DependenciesData>}
|
|
108
|
-
*/
|
|
109
|
-
async function searchDeps ({ limit, offset, outputJson }, spinner) {
|
|
110
|
-
const socketSdk = await setupSdk(getDefaultKey())
|
|
111
|
-
const result = await handleApiCall(socketSdk.searchDependencies({ limit, offset }), 'Searching dependencies')
|
|
112
|
-
|
|
113
|
-
if (!result.success) {
|
|
114
|
-
return handleUnsuccessfulApiResponse('searchDependencies', result, spinner)
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
spinner.stop()
|
|
118
|
-
|
|
119
|
-
console.log('Organization dependencies: \n')
|
|
120
|
-
|
|
121
|
-
if (outputJson) {
|
|
122
|
-
return console.log(result.data)
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const options = {
|
|
126
|
-
columns: [
|
|
127
|
-
{ field: 'namespace', name: chalk.cyan('Namespace') },
|
|
128
|
-
{ field: 'name', name: chalk.cyan('Name') },
|
|
129
|
-
{ field: 'version', name: chalk.cyan('Version') },
|
|
130
|
-
{ field: 'repository', name: chalk.cyan('Repository') },
|
|
131
|
-
{ field: 'branch', name: chalk.cyan('Branch') },
|
|
132
|
-
{ field: 'type', name: chalk.cyan('Type') },
|
|
133
|
-
{ field: 'direct', name: chalk.cyan('Direct') }
|
|
134
|
-
]
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const formattedResults = result.data.rows.map((/** @type {{[key:string]: any}} */ d) => {
|
|
138
|
-
return {
|
|
139
|
-
...d
|
|
140
|
-
}
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
const table = chalkTable(options, formattedResults)
|
|
144
|
-
|
|
145
|
-
console.log(table, '\n')
|
|
146
|
-
|
|
147
|
-
return {
|
|
148
|
-
data: result.data
|
|
149
|
-
}
|
|
150
|
-
}
|
package/lib/commands/index.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export * from './cdxgen/index.js'
|
|
2
|
-
export * from './info/index.js'
|
|
3
|
-
export * from './login/index.js'
|
|
4
|
-
export * from './logout/index.js'
|
|
5
|
-
export * from './npm/index.js'
|
|
6
|
-
export * from './npx/index.js'
|
|
7
|
-
export * from './raw-npm/index.js'
|
|
8
|
-
export * from './raw-npx/index.js'
|
|
9
|
-
export * from './report/index.js'
|
|
10
|
-
export * from './wrapper/index.js'
|
|
11
|
-
export * from './scan/index.js'
|
|
12
|
-
export * from './audit-log/index.js'
|
|
13
|
-
export * from './repos/index.js'
|
|
14
|
-
export * from './dependencies/index.js'
|
|
@@ -1,287 +0,0 @@
|
|
|
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, validationFlags } from '../../flags/index.js'
|
|
8
|
-
import { handleApiCall, handleUnsuccessfulApiResponse } from '../../utils/api-helpers.js'
|
|
9
|
-
import { ChalkOrMarkdown } from '../../utils/chalk-markdown.js'
|
|
10
|
-
import { prepareFlags } from '../../utils/flags.js'
|
|
11
|
-
import { formatSeverityCount, getCountSeverity } from '../../utils/format-issues.js'
|
|
12
|
-
import { printFlagList } from '../../utils/formatting.js'
|
|
13
|
-
import { objectSome } from '../../utils/misc.js'
|
|
14
|
-
import { FREE_API_KEY, getDefaultKey, setupSdk } from '../../utils/sdk.js'
|
|
15
|
-
|
|
16
|
-
/** @type {import('../../utils/meow-with-subcommands.js').CliSubcommand} */
|
|
17
|
-
export const info = {
|
|
18
|
-
description: 'Look up info regarding a package',
|
|
19
|
-
async run (argv, importMeta, { parentName }) {
|
|
20
|
-
const name = parentName + ' info'
|
|
21
|
-
|
|
22
|
-
const input = setupCommand(name, info.description, argv, importMeta)
|
|
23
|
-
if (input) {
|
|
24
|
-
const spinnerText = `Looking up data for packages: ${input.packages.join(', ')}\n`
|
|
25
|
-
const spinner = ora(spinnerText).start()
|
|
26
|
-
const packageData = await fetchPackageData(input.packages, input.includeAlerts, spinner)
|
|
27
|
-
if (packageData) {
|
|
28
|
-
formatPackageDataOutput(packageData, { name, ...input }, spinner)
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const infoFlags = prepareFlags({
|
|
35
|
-
// At the moment in API v0, alerts and license do the same thing.
|
|
36
|
-
// The license parameter will be implemented later.
|
|
37
|
-
// license: {
|
|
38
|
-
// type: 'boolean',
|
|
39
|
-
// shortFlag: 'l',
|
|
40
|
-
// default: false,
|
|
41
|
-
// description: 'Include license - Default is false',
|
|
42
|
-
// },
|
|
43
|
-
alerts: {
|
|
44
|
-
type: 'boolean',
|
|
45
|
-
shortFlag: 'a',
|
|
46
|
-
default: false,
|
|
47
|
-
description: 'Include alerts - Default is false',
|
|
48
|
-
}
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
// Internal functions
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* @typedef CommandContext
|
|
55
|
-
* @property {boolean} includeAlerts
|
|
56
|
-
* @property {boolean} outputJson
|
|
57
|
-
* @property {boolean} outputMarkdown
|
|
58
|
-
* @property {string[]} packages
|
|
59
|
-
* @property {boolean} strict
|
|
60
|
-
*/
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* @param {string} name
|
|
64
|
-
* @param {string} description
|
|
65
|
-
* @param {readonly string[]} argv
|
|
66
|
-
* @param {ImportMeta} importMeta
|
|
67
|
-
* @returns {void|CommandContext}
|
|
68
|
-
*/
|
|
69
|
-
function setupCommand (name, description, argv, importMeta) {
|
|
70
|
-
const flags = {
|
|
71
|
-
...outputFlags,
|
|
72
|
-
...validationFlags,
|
|
73
|
-
...infoFlags
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const cli = meow(`
|
|
77
|
-
Usage
|
|
78
|
-
$ ${name} <ecosystem>:<name>@<version>
|
|
79
|
-
|
|
80
|
-
Options
|
|
81
|
-
${printFlagList(flags, 6)}
|
|
82
|
-
|
|
83
|
-
Examples
|
|
84
|
-
$ ${name} npm:webtorrent
|
|
85
|
-
$ ${name} npm:webtorrent@1.9.1
|
|
86
|
-
`, {
|
|
87
|
-
argv,
|
|
88
|
-
description,
|
|
89
|
-
importMeta,
|
|
90
|
-
flags
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
const {
|
|
94
|
-
alerts: includeAlerts,
|
|
95
|
-
json: outputJson,
|
|
96
|
-
markdown: outputMarkdown,
|
|
97
|
-
strict,
|
|
98
|
-
} = cli.flags
|
|
99
|
-
|
|
100
|
-
const [rawPkgName = ''] = cli.input
|
|
101
|
-
|
|
102
|
-
if (!rawPkgName) {
|
|
103
|
-
console.error(`${chalk.bgRed('Input error')}: Please provide an ecosystem and package name`)
|
|
104
|
-
cli.showHelp()
|
|
105
|
-
return
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const /** @type {string[]} */inputPkgs = []
|
|
109
|
-
|
|
110
|
-
cli.input.map(pkg => {
|
|
111
|
-
const ecosystem = pkg.split(':')[0]
|
|
112
|
-
if (!ecosystem) {
|
|
113
|
-
console.error(`Package name ${pkg} formatted incorrectly.`)
|
|
114
|
-
return cli.showHelp()
|
|
115
|
-
} else {
|
|
116
|
-
const versionSeparator = pkg.lastIndexOf('@')
|
|
117
|
-
const ecosystemSeparator = pkg.lastIndexOf(ecosystem)
|
|
118
|
-
const pkgName = versionSeparator < 1 ? pkg.slice(ecosystemSeparator + ecosystem.length + 1) : pkg.slice(ecosystemSeparator + ecosystem.length + 1, versionSeparator)
|
|
119
|
-
const pkgVersion = versionSeparator < 1 ? 'latest' : pkg.slice(versionSeparator + 1)
|
|
120
|
-
inputPkgs.push(`${ecosystem}/${pkgName}@${pkgVersion}`)
|
|
121
|
-
}
|
|
122
|
-
return inputPkgs
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
return {
|
|
126
|
-
includeAlerts,
|
|
127
|
-
outputJson,
|
|
128
|
-
outputMarkdown,
|
|
129
|
-
packages: inputPkgs,
|
|
130
|
-
strict,
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* @typedef PackageData
|
|
136
|
-
* @property {import('@socketsecurity/sdk').SocketSdkReturnType<'batchPackageFetch'>["data"]} data
|
|
137
|
-
*/
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* @param {string[]} packages
|
|
141
|
-
* @param {boolean} includeAlerts
|
|
142
|
-
* @param {import('ora').Ora} spinner
|
|
143
|
-
* @returns {Promise<void|PackageData>}
|
|
144
|
-
*/
|
|
145
|
-
async function fetchPackageData (packages, includeAlerts, spinner) {
|
|
146
|
-
const socketSdk = await setupSdk(getDefaultKey() || FREE_API_KEY)
|
|
147
|
-
|
|
148
|
-
const components = packages.map(pkg => {
|
|
149
|
-
return { 'purl': `pkg:${pkg}` }
|
|
150
|
-
})
|
|
151
|
-
|
|
152
|
-
const result = await handleApiCall(socketSdk.batchPackageFetch(
|
|
153
|
-
{ alerts: includeAlerts.toString() },
|
|
154
|
-
{
|
|
155
|
-
components
|
|
156
|
-
}), 'looking up package')
|
|
157
|
-
|
|
158
|
-
if (!result.success) {
|
|
159
|
-
return handleUnsuccessfulApiResponse('batchPackageFetch', result, spinner)
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// @ts-ignore
|
|
163
|
-
result.data.map(pkg => {
|
|
164
|
-
const severityCount = pkg.alerts && getCountSeverity(pkg.alerts, includeAlerts ? undefined : 'high')
|
|
165
|
-
pkg.severityCount = severityCount
|
|
166
|
-
return pkg
|
|
167
|
-
})
|
|
168
|
-
|
|
169
|
-
spinner.stop()
|
|
170
|
-
|
|
171
|
-
return {
|
|
172
|
-
data: result.data
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* @param {CommandContext} data
|
|
178
|
-
* @param {{ name: string } & CommandContext} context
|
|
179
|
-
* @param {import('ora').Ora} spinner
|
|
180
|
-
* @returns {void}
|
|
181
|
-
*/
|
|
182
|
-
function formatPackageDataOutput (/** @type {{ [key: string]: any }} */ { data }, { outputJson, outputMarkdown, strict }, spinner) {
|
|
183
|
-
if (outputJson) {
|
|
184
|
-
console.log(JSON.stringify(data, undefined, 2))
|
|
185
|
-
} else {
|
|
186
|
-
data.map((/** @type {{[key:string]: any}} */ d) => {
|
|
187
|
-
const { score, license, name, severityCount, version } = d
|
|
188
|
-
console.log(`\nPackage metrics for ${name}:`)
|
|
189
|
-
|
|
190
|
-
const scoreResult = {
|
|
191
|
-
'Supply Chain Risk': Math.floor(score.supplyChain * 100),
|
|
192
|
-
'Maintenance': Math.floor(score.maintenance * 100),
|
|
193
|
-
'Quality': Math.floor(score.quality * 100),
|
|
194
|
-
'Vulnerabilities': Math.floor(score.vulnerability * 100),
|
|
195
|
-
'License': Math.floor(score.license * 100),
|
|
196
|
-
'Overall': Math.floor(score.overall * 100)
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
Object.entries(scoreResult).map(score => console.log(`- ${score[0]}: ${formatScore(score[1])}`))
|
|
200
|
-
|
|
201
|
-
// Package license
|
|
202
|
-
console.log('\nPackage license:')
|
|
203
|
-
console.log(`${license}`)
|
|
204
|
-
|
|
205
|
-
// Package issues list
|
|
206
|
-
if (objectSome(severityCount)) {
|
|
207
|
-
const issueSummary = formatSeverityCount(severityCount)
|
|
208
|
-
console.log('\n')
|
|
209
|
-
spinner[strict ? 'fail' : 'succeed'](`Package has these issues: ${issueSummary}`)
|
|
210
|
-
formatPackageIssuesDetails(data.alerts, outputMarkdown)
|
|
211
|
-
} else if (severityCount && !objectSome(severityCount)) {
|
|
212
|
-
console.log('\n')
|
|
213
|
-
spinner.succeed('Package has no issues')
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Link to issues list
|
|
217
|
-
const format = new ChalkOrMarkdown(!!outputMarkdown)
|
|
218
|
-
const url = `https://socket.dev/npm/package/${name}/overview/${version}`
|
|
219
|
-
if (version === 'latest') {
|
|
220
|
-
console.log('\nDetailed info on socket.dev: ' + format.hyperlink(`${name}`, url, { fallbackToUrl: true }))
|
|
221
|
-
} else {
|
|
222
|
-
console.log('\nDetailed info on socket.dev: ' + format.hyperlink(`${name} v${version}`, url, { fallbackToUrl: true }))
|
|
223
|
-
}
|
|
224
|
-
if (!outputMarkdown) {
|
|
225
|
-
console.log(chalk.dim('\nOr rerun', chalk.italic(name), 'using the', chalk.italic('--json'), 'flag to get full JSON output'))
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
if (strict && objectSome(severityCount)) {
|
|
229
|
-
process.exit(1)
|
|
230
|
-
}
|
|
231
|
-
return d
|
|
232
|
-
})
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* @param {{[key: string]: any}[]} alertsData
|
|
238
|
-
* @param {boolean} outputMarkdown
|
|
239
|
-
* @returns {void[]}
|
|
240
|
-
*/
|
|
241
|
-
function formatPackageIssuesDetails (alertsData, outputMarkdown) {
|
|
242
|
-
const issueDetails = alertsData.filter(d => d['severity'] === 'high' || d['severity'] === 'critical')
|
|
243
|
-
|
|
244
|
-
const uniqueIssues = issueDetails.reduce((/** @type {{ [key: string]: {count: Number, label: string | undefined} }} */ acc, issue) => {
|
|
245
|
-
const { type } = issue
|
|
246
|
-
if (type) {
|
|
247
|
-
if (!acc[type]) {
|
|
248
|
-
acc[type] = {
|
|
249
|
-
label: issue['type'],
|
|
250
|
-
count: 1
|
|
251
|
-
}
|
|
252
|
-
} else {
|
|
253
|
-
// @ts-ignore
|
|
254
|
-
acc[type].count += 1
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
return acc
|
|
258
|
-
}, {})
|
|
259
|
-
|
|
260
|
-
const format = new ChalkOrMarkdown(!!outputMarkdown)
|
|
261
|
-
|
|
262
|
-
return Object.keys(uniqueIssues).map(issue => {
|
|
263
|
-
const issueWithLink = format.hyperlink(`${uniqueIssues[issue]?.label}`, `https://socket.dev/npm/issue/${issue}`, { fallbackToUrl: true })
|
|
264
|
-
if (uniqueIssues[issue]?.count === 1) {
|
|
265
|
-
return console.log(`- ${issueWithLink}`)
|
|
266
|
-
}
|
|
267
|
-
return console.log(`- ${issueWithLink}: ${uniqueIssues[issue]?.count}`)
|
|
268
|
-
})
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* @param {number} score
|
|
273
|
-
* @returns {string}
|
|
274
|
-
*/
|
|
275
|
-
function formatScore (score) {
|
|
276
|
-
const error = chalk.hex('#de7c7b')
|
|
277
|
-
const warning = chalk.hex('#e59361')
|
|
278
|
-
const success = chalk.hex('#a4cb9d')
|
|
279
|
-
|
|
280
|
-
if (score > 80) {
|
|
281
|
-
return `${success(score)}`
|
|
282
|
-
} else if (score < 80 && score > 60) {
|
|
283
|
-
return `${warning(score)}`
|
|
284
|
-
} else {
|
|
285
|
-
return `${error(score)}`
|
|
286
|
-
}
|
|
287
|
-
}
|