@socketsecurity/cli-with-sentry 0.14.154 → 0.15.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/dist/.config/tsconfig.dts.tsbuildinfo +1 -1
- package/dist/cli.js +1470 -2680
- package/dist/cli.js.map +1 -1
- package/dist/constants.js +15 -12
- package/dist/constants.js.map +1 -1
- package/dist/instrument-with-sentry.js +10 -3
- package/dist/instrument-with-sentry.js.map +1 -1
- package/dist/shadow-bin.js +5 -7
- package/dist/shadow-bin.js.map +1 -1
- package/dist/shadow-npm-inject.js +130 -1646
- package/dist/shadow-npm-inject.js.map +1 -1
- package/dist/types/commands/analytics/cmd-analytics.d.mts.map +1 -1
- package/dist/types/commands/analytics/fetch-org-analytics.d.mts.map +1 -1
- package/dist/types/commands/analytics/fetch-repo-analytics.d.mts.map +1 -1
- package/dist/types/commands/analytics/handle-analytics.d.mts.map +1 -1
- package/dist/types/commands/analytics/output-analytics.d.mts +15 -16
- package/dist/types/commands/analytics/output-analytics.d.mts.map +1 -1
- package/dist/types/commands/audit-log/fetch-audit-log.d.mts.map +1 -1
- package/dist/types/commands/cdxgen/cmd-cdxgen.d.mts.map +1 -1
- package/dist/types/commands/ci/fetch-default-org-slug.d.mts.map +1 -1
- package/dist/types/commands/dependencies/fetch-dependencies.d.mts.map +1 -1
- package/dist/types/commands/diff-scan/fetch-diff-scan.d.mts.map +1 -1
- package/dist/types/commands/fix/git.d.mts +9 -1
- package/dist/types/commands/fix/git.d.mts.map +1 -1
- package/dist/types/commands/fix/npm-fix.d.mts.map +1 -1
- package/dist/types/commands/fix/open-pr.d.mts +30 -16
- package/dist/types/commands/fix/open-pr.d.mts.map +1 -1
- package/dist/types/commands/fix/pnpm-fix.d.mts.map +1 -1
- package/dist/types/commands/info/fetch-package-info.d.mts.map +1 -1
- package/dist/types/commands/login/attempt-login.d.mts.map +1 -1
- package/dist/types/commands/npm/wrap-npm.d.mts.map +1 -1
- package/dist/types/commands/npx/wrap-npx.d.mts.map +1 -1
- package/dist/types/commands/oops/cmd-oops.d.mts.map +1 -1
- package/dist/types/commands/optimize/deps-includes-by-agent.d.mts +1 -1
- package/dist/types/commands/optimize/get-overrides-by-agent.d.mts +1 -1
- package/dist/types/commands/optimize/lockfile-includes-by-agent.d.mts +1 -1
- package/dist/types/commands/optimize/ls-by-agent.d.mts +1 -1
- package/dist/types/commands/optimize/update-manifest-by-agent.d.mts +1 -1
- package/dist/types/commands/organization/fetch-license-policy.d.mts.map +1 -1
- package/dist/types/commands/organization/fetch-organization-list.d.mts.map +1 -1
- package/dist/types/commands/organization/fetch-quota.d.mts.map +1 -1
- package/dist/types/commands/organization/fetch-security-policy.d.mts.map +1 -1
- package/dist/types/commands/organization/output-organization-list.d.mts.map +1 -1
- package/dist/types/commands/package/cmd-package-score.d.mts.map +1 -1
- package/dist/types/commands/package/fetch-purl-deep-score.d.mts.map +1 -1
- package/dist/types/commands/package/fetch-purls-shallow-score.d.mts.map +1 -1
- package/dist/types/commands/repos/fetch-create-repo.d.mts.map +1 -1
- package/dist/types/commands/repos/fetch-delete-repo.d.mts.map +1 -1
- package/dist/types/commands/repos/fetch-list-repos.d.mts.map +1 -1
- package/dist/types/commands/repos/fetch-update-repo.d.mts.map +1 -1
- package/dist/types/commands/repos/fetch-view-repo.d.mts.map +1 -1
- package/dist/types/commands/repos/handle-create-repo.d.mts.map +1 -1
- package/dist/types/commands/repos/output-create-repo.d.mts +1 -1
- package/dist/types/commands/repos/output-create-repo.d.mts.map +1 -1
- package/dist/types/commands/scan/fetch-create-org-full-scan.d.mts.map +1 -1
- package/dist/types/commands/scan/fetch-delete-org-full-scan.d.mts.map +1 -1
- package/dist/types/commands/scan/fetch-diff-scan.d.mts.map +1 -1
- package/dist/types/commands/scan/fetch-list-scans.d.mts.map +1 -1
- package/dist/types/commands/scan/fetch-report-data.d.mts.map +1 -1
- package/dist/types/commands/scan/fetch-scan-metadata.d.mts.map +1 -1
- package/dist/types/commands/scan/fetch-scan.d.mts.map +1 -1
- package/dist/types/commands/scan/fetch-supported-scan-file-names.d.mts.map +1 -1
- package/dist/types/commands/scan/generate-report.d.mts.map +1 -1
- package/dist/types/commands/scan/stream-scan.d.mts +1 -1
- package/dist/types/commands/scan/stream-scan.d.mts.map +1 -1
- package/dist/types/commands/scan/suggest-org-slug.d.mts.map +1 -1
- package/dist/types/commands/scan/suggest-repo-slug.d.mts.map +1 -1
- package/dist/types/commands/threat-feed/fetch-threat-feed.d.mts.map +1 -1
- package/dist/types/commands/threat-feed/output-threat-feed.d.mts.map +1 -1
- package/dist/types/constants.d.mts +1 -1
- package/dist/types/constants.d.mts.map +1 -1
- package/dist/types/shadow/npm/arborist/lib/dep-valid.d.mts +2 -2
- package/dist/types/shadow/npm/arborist/lib/dep-valid.d.mts.map +1 -1
- package/dist/types/{utils → shadow/npm}/arborist-helpers.d.mts +19 -3
- package/dist/types/shadow/npm/arborist-helpers.d.mts.map +1 -0
- package/dist/types/{utils/npm.d.mts → shadow/npm/install.d.mts} +2 -2
- package/dist/types/shadow/npm/install.d.mts.map +1 -0
- package/dist/types/shadow/npm/paths.d.mts +0 -6
- package/dist/types/shadow/npm/paths.d.mts.map +1 -1
- package/dist/types/utils/agent.d.mts +2 -2
- package/dist/types/utils/agent.d.mts.map +1 -1
- package/dist/types/utils/alerts-map.d.mts +0 -11
- package/dist/types/utils/alerts-map.d.mts.map +1 -1
- package/dist/types/utils/api.d.mts +22 -12
- package/dist/types/utils/api.d.mts.map +1 -1
- package/dist/types/utils/meow-with-subcommands.d.mts.map +1 -1
- package/dist/types/utils/npm-paths.d.mts +7 -0
- package/dist/types/utils/npm-paths.d.mts.map +1 -0
- package/dist/types/utils/pnpm.d.mts +2 -1
- package/dist/types/utils/pnpm.d.mts.map +1 -1
- package/dist/types/utils/sdk.d.mts +4 -1
- package/dist/types/utils/sdk.d.mts.map +1 -1
- package/dist/types/utils/socket-url.d.mts +10 -2
- package/dist/types/utils/socket-url.d.mts.map +1 -1
- package/dist/types/utils/translations.d.mts.map +1 -1
- package/dist/utils.js +3323 -0
- package/dist/utils.js.map +1 -0
- package/dist/vendor.js +1284 -1231
- package/dist/vendor.js.map +1 -1
- package/external/@socketsecurity/registry/external/browserslist.js +382 -366
- package/external/@socketsecurity/registry/external/browserslist.js.map +1 -1
- package/external/@socketsecurity/registry/lib/constants/maintained-node-versions.js +10 -16
- package/external/@socketsecurity/registry/lib/fs.d.ts +6 -4
- package/external/@socketsecurity/registry/package.json +7 -7
- package/package.json +21 -21
- package/dist/shadow-npm-paths.js +0 -291
- package/dist/shadow-npm-paths.js.map +0 -1
- package/dist/types/utils/arborist-helpers.d.mts.map +0 -1
- package/dist/types/utils/npm.d.mts.map +0 -1
package/dist/utils.js
ADDED
|
@@ -0,0 +1,3323 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const vendor = require('./vendor.js')
|
|
4
|
+
const logger = require('../external/@socketsecurity/registry/lib/logger')
|
|
5
|
+
const debug = require('../external/@socketsecurity/registry/lib/debug')
|
|
6
|
+
const path = require('node:path')
|
|
7
|
+
const objects = require('../external/@socketsecurity/registry/lib/objects')
|
|
8
|
+
const path$1 = require('../external/@socketsecurity/registry/lib/path')
|
|
9
|
+
const regexps = require('../external/@socketsecurity/registry/lib/regexps')
|
|
10
|
+
const constants = require('./constants.js')
|
|
11
|
+
const prompts = require('../external/@socketsecurity/registry/lib/prompts')
|
|
12
|
+
const strings = require('../external/@socketsecurity/registry/lib/strings')
|
|
13
|
+
const promises = require('node:timers/promises')
|
|
14
|
+
const arrays = require('../external/@socketsecurity/registry/lib/arrays')
|
|
15
|
+
const packages = require('../external/@socketsecurity/registry/lib/packages')
|
|
16
|
+
const fs = require('node:fs')
|
|
17
|
+
const os = require('node:os')
|
|
18
|
+
const registry = require('../external/@socketsecurity/registry')
|
|
19
|
+
const sorts = require('../external/@socketsecurity/registry/lib/sorts')
|
|
20
|
+
const Module = require('node:module')
|
|
21
|
+
const spawn = require('../external/@socketsecurity/registry/lib/spawn')
|
|
22
|
+
const npm = require('../external/@socketsecurity/registry/lib/npm')
|
|
23
|
+
const words = require('../external/@socketsecurity/registry/lib/words')
|
|
24
|
+
const fs$1 = require('../external/@socketsecurity/registry/lib/fs')
|
|
25
|
+
|
|
26
|
+
const _documentCurrentScript =
|
|
27
|
+
typeof document !== 'undefined' ? document.currentScript : null
|
|
28
|
+
const { NPM: NPM$6, PNPM: PNPM$2 } = constants
|
|
29
|
+
const PNPM_WORKSPACE = `${PNPM$2}-workspace`
|
|
30
|
+
const ignoredDirs = [
|
|
31
|
+
// Taken from ignore-by-default:
|
|
32
|
+
// https://github.com/novemberborn/ignore-by-default/blob/v2.1.0/index.js
|
|
33
|
+
'.git',
|
|
34
|
+
// Git repository files, see <https://git-scm.com/>
|
|
35
|
+
'.log',
|
|
36
|
+
// Log files emitted by tools such as `tsserver`, see <https://github.com/Microsoft/TypeScript/wiki/Standalone-Server-%28tsserver%29>
|
|
37
|
+
'.nyc_output',
|
|
38
|
+
// Temporary directory where nyc stores coverage data, see <https://github.com/bcoe/nyc>
|
|
39
|
+
'.sass-cache',
|
|
40
|
+
// Cache folder for node-sass, see <https://github.com/sass/node-sass>
|
|
41
|
+
'.yarn',
|
|
42
|
+
// Where node modules are installed when using Yarn, see <https://yarnpkg.com/>
|
|
43
|
+
'bower_components',
|
|
44
|
+
// Where Bower packages are installed, see <http://bower.io/>
|
|
45
|
+
'coverage',
|
|
46
|
+
// Standard output directory for code coverage reports, see <https://github.com/gotwarlost/istanbul>
|
|
47
|
+
'node_modules',
|
|
48
|
+
// Where Node modules are installed, see <https://nodejs.org/>
|
|
49
|
+
// Taken from globby:
|
|
50
|
+
// https://github.com/sindresorhus/globby/blob/v14.0.2/ignore.js#L11-L16
|
|
51
|
+
'flow-typed'
|
|
52
|
+
]
|
|
53
|
+
const ignoredDirPatterns = ignoredDirs.map(i => `**/${i}`)
|
|
54
|
+
async function getWorkspaceGlobs(agent, cwd = process.cwd()) {
|
|
55
|
+
let workspacePatterns
|
|
56
|
+
if (agent === PNPM$2) {
|
|
57
|
+
for (const workspacePath of [
|
|
58
|
+
path.join(cwd, `${PNPM_WORKSPACE}.yaml`),
|
|
59
|
+
path.join(cwd, `${PNPM_WORKSPACE}.yml`)
|
|
60
|
+
]) {
|
|
61
|
+
// eslint-disable-next-line no-await-in-loop
|
|
62
|
+
const yml = await safeReadFile(workspacePath)
|
|
63
|
+
if (yml) {
|
|
64
|
+
try {
|
|
65
|
+
workspacePatterns = vendor.distExports$1.parse(yml)?.packages
|
|
66
|
+
} catch {}
|
|
67
|
+
if (workspacePatterns) {
|
|
68
|
+
break
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
workspacePatterns = (
|
|
74
|
+
await packages.readPackageJson(cwd, {
|
|
75
|
+
throws: false
|
|
76
|
+
})
|
|
77
|
+
)?.['workspaces']
|
|
78
|
+
}
|
|
79
|
+
return Array.isArray(workspacePatterns)
|
|
80
|
+
? workspacePatterns
|
|
81
|
+
.filter(strings.isNonEmptyString)
|
|
82
|
+
.map(workspacePatternToGlobPattern)
|
|
83
|
+
: []
|
|
84
|
+
}
|
|
85
|
+
function ignoreFileLinesToGlobPatterns(lines, filepath, cwd) {
|
|
86
|
+
const base = path.relative(cwd, path.dirname(filepath)).replace(/\\/g, '/')
|
|
87
|
+
const patterns = []
|
|
88
|
+
for (let i = 0, { length } = lines; i < length; i += 1) {
|
|
89
|
+
const pattern = lines[i].trim()
|
|
90
|
+
if (pattern.length > 0 && pattern.charCodeAt(0) !== 35 /*'#'*/) {
|
|
91
|
+
patterns.push(
|
|
92
|
+
ignorePatternToMinimatch(
|
|
93
|
+
pattern.length && pattern.charCodeAt(0) === 33 /*'!'*/
|
|
94
|
+
? `!${path.posix.join(base, pattern.slice(1))}`
|
|
95
|
+
: path.posix.join(base, pattern)
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return patterns
|
|
101
|
+
}
|
|
102
|
+
function ignoreFileToGlobPatterns(content, filepath, cwd) {
|
|
103
|
+
return ignoreFileLinesToGlobPatterns(content.split(/\r?\n/), filepath, cwd)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Based on `@eslint/compat` convertIgnorePatternToMinimatch.
|
|
107
|
+
// Apache v2.0 licensed
|
|
108
|
+
// Copyright Nicholas C. Zakas
|
|
109
|
+
// https://github.com/eslint/rewrite/blob/compat-v1.2.1/packages/compat/src/ignore-file.js#L28
|
|
110
|
+
function ignorePatternToMinimatch(pattern) {
|
|
111
|
+
const isNegated = pattern.startsWith('!')
|
|
112
|
+
const negatedPrefix = isNegated ? '!' : ''
|
|
113
|
+
const patternToTest = (isNegated ? pattern.slice(1) : pattern).trimEnd()
|
|
114
|
+
// Special cases.
|
|
115
|
+
if (
|
|
116
|
+
patternToTest === '' ||
|
|
117
|
+
patternToTest === '**' ||
|
|
118
|
+
patternToTest === '/**' ||
|
|
119
|
+
patternToTest === '**'
|
|
120
|
+
) {
|
|
121
|
+
return `${negatedPrefix}${patternToTest}`
|
|
122
|
+
}
|
|
123
|
+
const firstIndexOfSlash = patternToTest.indexOf('/')
|
|
124
|
+
const matchEverywherePrefix =
|
|
125
|
+
firstIndexOfSlash === -1 || firstIndexOfSlash === patternToTest.length - 1
|
|
126
|
+
? '**/'
|
|
127
|
+
: ''
|
|
128
|
+
const patternWithoutLeadingSlash =
|
|
129
|
+
firstIndexOfSlash === 0 ? patternToTest.slice(1) : patternToTest
|
|
130
|
+
// Escape `{` and `(` because in gitignore patterns they are just
|
|
131
|
+
// literal characters without any specific syntactic meaning,
|
|
132
|
+
// while in minimatch patterns they can form brace expansion or extglob syntax.
|
|
133
|
+
//
|
|
134
|
+
// For example, gitignore pattern `src/{a,b}.js` ignores file `src/{a,b}.js`.
|
|
135
|
+
// But, the same minimatch pattern `src/{a,b}.js` ignores files `src/a.js` and `src/b.js`.
|
|
136
|
+
// Minimatch pattern `src/\{a,b}.js` is equivalent to gitignore pattern `src/{a,b}.js`.
|
|
137
|
+
const escapedPatternWithoutLeadingSlash =
|
|
138
|
+
patternWithoutLeadingSlash.replaceAll(
|
|
139
|
+
/(?=((?:\\.|[^{(])*))\1([{(])/guy,
|
|
140
|
+
'$1\\$2'
|
|
141
|
+
)
|
|
142
|
+
const matchInsideSuffix = patternToTest.endsWith('/**') ? '/*' : ''
|
|
143
|
+
return `${negatedPrefix}${matchEverywherePrefix}${escapedPatternWithoutLeadingSlash}${matchInsideSuffix}`
|
|
144
|
+
}
|
|
145
|
+
function workspacePatternToGlobPattern(workspace) {
|
|
146
|
+
const { length } = workspace
|
|
147
|
+
if (!length) {
|
|
148
|
+
return ''
|
|
149
|
+
}
|
|
150
|
+
// If the workspace ends with "/"
|
|
151
|
+
if (workspace.charCodeAt(length - 1) === 47 /*'/'*/) {
|
|
152
|
+
return `${workspace}/*/package.json`
|
|
153
|
+
}
|
|
154
|
+
// If the workspace ends with "/**"
|
|
155
|
+
if (
|
|
156
|
+
workspace.charCodeAt(length - 1) === 42 /*'*'*/ &&
|
|
157
|
+
workspace.charCodeAt(length - 2) === 42 /*'*'*/ &&
|
|
158
|
+
workspace.charCodeAt(length - 3) === 47 /*'/'*/
|
|
159
|
+
) {
|
|
160
|
+
return `${workspace}/*/**/package.json`
|
|
161
|
+
}
|
|
162
|
+
// Things like "packages/a" or "packages/*"
|
|
163
|
+
return `${workspace}/package.json`
|
|
164
|
+
}
|
|
165
|
+
async function filterGlobResultToSupportedFiles(entries, supportedFiles) {
|
|
166
|
+
const patterns = ['golang', NPM$6, 'maven', 'pypi', 'gem', 'nuget'].reduce(
|
|
167
|
+
(r, n) => {
|
|
168
|
+
const supported = supportedFiles[n]
|
|
169
|
+
r.push(
|
|
170
|
+
...(supported
|
|
171
|
+
? Object.values(supported).map(p => `**/${p.pattern}`)
|
|
172
|
+
: [])
|
|
173
|
+
)
|
|
174
|
+
return r
|
|
175
|
+
},
|
|
176
|
+
[]
|
|
177
|
+
)
|
|
178
|
+
return entries.filter(p => vendor.micromatchExports.some(p, patterns))
|
|
179
|
+
}
|
|
180
|
+
async function globWithGitIgnore(patterns, options) {
|
|
181
|
+
const {
|
|
182
|
+
cwd = process.cwd(),
|
|
183
|
+
socketConfig,
|
|
184
|
+
...additionalOptions
|
|
185
|
+
} = {
|
|
186
|
+
__proto__: null,
|
|
187
|
+
...options
|
|
188
|
+
}
|
|
189
|
+
const projectIgnorePaths = socketConfig?.projectIgnorePaths
|
|
190
|
+
const ignoreFiles = await vendor.distExports.glob(['**/.gitignore'], {
|
|
191
|
+
absolute: true,
|
|
192
|
+
cwd,
|
|
193
|
+
expandDirectories: true
|
|
194
|
+
})
|
|
195
|
+
const ignores = [
|
|
196
|
+
...ignoredDirPatterns,
|
|
197
|
+
...(Array.isArray(projectIgnorePaths)
|
|
198
|
+
? ignoreFileLinesToGlobPatterns(
|
|
199
|
+
projectIgnorePaths,
|
|
200
|
+
path.join(cwd, '.gitignore'),
|
|
201
|
+
cwd
|
|
202
|
+
)
|
|
203
|
+
: []),
|
|
204
|
+
...(
|
|
205
|
+
await Promise.all(
|
|
206
|
+
ignoreFiles.map(async filepath =>
|
|
207
|
+
ignoreFileToGlobPatterns(
|
|
208
|
+
await fs.promises.readFile(filepath, 'utf8'),
|
|
209
|
+
filepath,
|
|
210
|
+
cwd
|
|
211
|
+
)
|
|
212
|
+
)
|
|
213
|
+
)
|
|
214
|
+
).flat()
|
|
215
|
+
]
|
|
216
|
+
const hasNegatedPattern = ignores.some(p => p.charCodeAt(0) === 33 /*'!'*/)
|
|
217
|
+
const globOptions = {
|
|
218
|
+
absolute: true,
|
|
219
|
+
cwd,
|
|
220
|
+
expandDirectories: false,
|
|
221
|
+
ignore: hasNegatedPattern ? [] : ignores,
|
|
222
|
+
...additionalOptions
|
|
223
|
+
}
|
|
224
|
+
const result = await vendor.distExports.glob(patterns, globOptions)
|
|
225
|
+
if (!hasNegatedPattern) {
|
|
226
|
+
return result
|
|
227
|
+
}
|
|
228
|
+
const { absolute } = globOptions
|
|
229
|
+
|
|
230
|
+
// Note: the input files must be INSIDE the cwd. If you get strange looking
|
|
231
|
+
// relative path errors here, most likely your path is outside the given cwd.
|
|
232
|
+
const filtered = vendor
|
|
233
|
+
.ignoreExports()
|
|
234
|
+
.add(ignores)
|
|
235
|
+
.filter(absolute ? result.map(p => path.relative(cwd, p)) : result)
|
|
236
|
+
return absolute ? filtered.map(p => path.resolve(cwd, p)) : filtered
|
|
237
|
+
}
|
|
238
|
+
async function globNodeModules(cwd = process.cwd()) {
|
|
239
|
+
return await vendor.distExports.glob('**/node_modules/**', {
|
|
240
|
+
absolute: true,
|
|
241
|
+
cwd
|
|
242
|
+
})
|
|
243
|
+
}
|
|
244
|
+
async function globWorkspace(agent, cwd = process.cwd()) {
|
|
245
|
+
const workspaceGlobs = await getWorkspaceGlobs(agent, cwd)
|
|
246
|
+
return workspaceGlobs.length
|
|
247
|
+
? await vendor.distExports.glob(workspaceGlobs, {
|
|
248
|
+
absolute: true,
|
|
249
|
+
cwd,
|
|
250
|
+
ignore: ['**/node_modules/**', '**/bower_components/**']
|
|
251
|
+
})
|
|
252
|
+
: []
|
|
253
|
+
}
|
|
254
|
+
function pathsToGlobPatterns(paths) {
|
|
255
|
+
// TODO: Does not support `~/` paths.
|
|
256
|
+
return paths.map(p => (p === '.' || p === './' ? '**/*' : p))
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const { abortSignal } = constants
|
|
260
|
+
async function removeNodeModules(cwd = process.cwd()) {
|
|
261
|
+
const nodeModulesPaths = await globNodeModules(cwd)
|
|
262
|
+
await Promise.all(nodeModulesPaths.map(p => fs$1.remove(p)))
|
|
263
|
+
}
|
|
264
|
+
async function findUp(name, { cwd = process.cwd(), signal = abortSignal }) {
|
|
265
|
+
let dir = path.resolve(cwd)
|
|
266
|
+
const { root } = path.parse(dir)
|
|
267
|
+
const names = [name].flat()
|
|
268
|
+
while (dir && dir !== root) {
|
|
269
|
+
for (const name of names) {
|
|
270
|
+
if (signal?.aborted) {
|
|
271
|
+
return undefined
|
|
272
|
+
}
|
|
273
|
+
const filePath = path.join(dir, name)
|
|
274
|
+
try {
|
|
275
|
+
// eslint-disable-next-line no-await-in-loop
|
|
276
|
+
const stats = await fs.promises.stat(filePath)
|
|
277
|
+
if (stats.isFile()) {
|
|
278
|
+
return filePath
|
|
279
|
+
}
|
|
280
|
+
} catch {}
|
|
281
|
+
}
|
|
282
|
+
dir = path.dirname(dir)
|
|
283
|
+
}
|
|
284
|
+
return undefined
|
|
285
|
+
}
|
|
286
|
+
async function readFileBinary(filepath, options) {
|
|
287
|
+
return await fs.promises.readFile(filepath, {
|
|
288
|
+
signal: abortSignal,
|
|
289
|
+
...options,
|
|
290
|
+
encoding: 'binary'
|
|
291
|
+
})
|
|
292
|
+
}
|
|
293
|
+
async function readFileUtf8(filepath, options) {
|
|
294
|
+
return await fs.promises.readFile(filepath, {
|
|
295
|
+
signal: abortSignal,
|
|
296
|
+
...options,
|
|
297
|
+
encoding: 'utf8'
|
|
298
|
+
})
|
|
299
|
+
}
|
|
300
|
+
async function safeReadFile(filepath, options) {
|
|
301
|
+
try {
|
|
302
|
+
return await fs.promises.readFile(filepath, {
|
|
303
|
+
encoding: 'utf8',
|
|
304
|
+
signal: abortSignal,
|
|
305
|
+
...(typeof options === 'string'
|
|
306
|
+
? {
|
|
307
|
+
encoding: options
|
|
308
|
+
}
|
|
309
|
+
: options)
|
|
310
|
+
})
|
|
311
|
+
} catch {}
|
|
312
|
+
return undefined
|
|
313
|
+
}
|
|
314
|
+
function safeReadFileSync(filepath, options) {
|
|
315
|
+
try {
|
|
316
|
+
return fs.readFileSync(filepath, {
|
|
317
|
+
encoding: 'utf8',
|
|
318
|
+
...(typeof options === 'string'
|
|
319
|
+
? {
|
|
320
|
+
encoding: options
|
|
321
|
+
}
|
|
322
|
+
: options)
|
|
323
|
+
})
|
|
324
|
+
} catch {}
|
|
325
|
+
return undefined
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const { LOCALAPPDATA, SOCKET_APP_DIR } = constants
|
|
329
|
+
const supportedConfigKeys = new Map([
|
|
330
|
+
['apiBaseUrl', 'Base URL of the API endpoint'],
|
|
331
|
+
['apiProxy', 'A proxy through which to access the API'],
|
|
332
|
+
['apiToken', 'The API token required to access most API endpoints'],
|
|
333
|
+
[
|
|
334
|
+
'defaultOrg',
|
|
335
|
+
'The default org slug to use; usually the org your API token has access to. When set, all orgSlug arguments are implied to be this value.'
|
|
336
|
+
],
|
|
337
|
+
[
|
|
338
|
+
'enforcedOrgs',
|
|
339
|
+
'Orgs in this list have their security policies enforced on this machine'
|
|
340
|
+
],
|
|
341
|
+
['isTestingV1', 'For development of testing the next major bump']
|
|
342
|
+
])
|
|
343
|
+
const sensitiveConfigKeys = new Set(['apiToken'])
|
|
344
|
+
let _cachedConfig
|
|
345
|
+
// When using --config or SOCKET_CLI_CONFIG, do not persist the config.
|
|
346
|
+
let _readOnlyConfig = false
|
|
347
|
+
function overrideCachedConfig(jsonConfig) {
|
|
348
|
+
debug.debugLog('Overriding entire config, marking config as read-only')
|
|
349
|
+
let config
|
|
350
|
+
try {
|
|
351
|
+
config = JSON.parse(String(jsonConfig))
|
|
352
|
+
if (!config || typeof config !== 'object') {
|
|
353
|
+
// `null` is valid json, so are primitive values. They're not valid config objects :)
|
|
354
|
+
return {
|
|
355
|
+
ok: false,
|
|
356
|
+
message: 'Could not parse Config as JSON',
|
|
357
|
+
cause:
|
|
358
|
+
"Could not JSON parse the config override. Make sure it's a proper JSON object (double-quoted keys and strings, no unquoted `undefined`) and try again."
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
} catch {
|
|
362
|
+
// Force set an empty config to prevent accidentally using system settings
|
|
363
|
+
_cachedConfig = {}
|
|
364
|
+
_readOnlyConfig = true
|
|
365
|
+
return {
|
|
366
|
+
ok: false,
|
|
367
|
+
message: 'Could not parse Config as JSON',
|
|
368
|
+
cause:
|
|
369
|
+
"Could not JSON parse the config override. Make sure it's a proper JSON object (double-quoted keys and strings, no unquoted `undefined`) and try again."
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// @ts-ignore Override an illegal object.
|
|
374
|
+
_cachedConfig = config
|
|
375
|
+
_readOnlyConfig = true
|
|
376
|
+
|
|
377
|
+
// Normalize apiKey to apiToken.
|
|
378
|
+
if (_cachedConfig['apiKey']) {
|
|
379
|
+
if (_cachedConfig['apiToken']) {
|
|
380
|
+
logger.logger.warn(
|
|
381
|
+
'Note: The config override had both apiToken and apiKey. Using the apiToken value. Remove the apiKey to get rid of this message.'
|
|
382
|
+
)
|
|
383
|
+
}
|
|
384
|
+
_cachedConfig['apiToken'] = _cachedConfig['apiKey']
|
|
385
|
+
delete _cachedConfig['apiKey']
|
|
386
|
+
}
|
|
387
|
+
return {
|
|
388
|
+
ok: true,
|
|
389
|
+
data: undefined
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
function overrideConfigApiToken(apiToken) {
|
|
393
|
+
debug.debugLog('Overriding API token, marking config as read-only')
|
|
394
|
+
// Set token to the local cached config and mark it read-only so it doesn't persist
|
|
395
|
+
_cachedConfig = {
|
|
396
|
+
...vendor.configExports,
|
|
397
|
+
...(apiToken === undefined
|
|
398
|
+
? {}
|
|
399
|
+
: {
|
|
400
|
+
apiToken: String(apiToken)
|
|
401
|
+
})
|
|
402
|
+
}
|
|
403
|
+
_readOnlyConfig = true
|
|
404
|
+
}
|
|
405
|
+
function getConfigValues() {
|
|
406
|
+
if (_cachedConfig === undefined) {
|
|
407
|
+
_cachedConfig = {}
|
|
408
|
+
// Order: env var > --config flag > file
|
|
409
|
+
const configPath = getConfigPath()
|
|
410
|
+
if (configPath) {
|
|
411
|
+
const raw = safeReadFileSync(configPath)
|
|
412
|
+
if (raw) {
|
|
413
|
+
try {
|
|
414
|
+
Object.assign(
|
|
415
|
+
_cachedConfig,
|
|
416
|
+
JSON.parse(Buffer.from(raw, 'base64').toString())
|
|
417
|
+
)
|
|
418
|
+
} catch {
|
|
419
|
+
logger.logger.warn(`Failed to parse config at ${configPath}`)
|
|
420
|
+
}
|
|
421
|
+
// Normalize apiKey to apiToken and persist it.
|
|
422
|
+
// This is a one time migration per user.
|
|
423
|
+
if (_cachedConfig['apiKey']) {
|
|
424
|
+
const token = _cachedConfig['apiKey']
|
|
425
|
+
delete _cachedConfig['apiKey']
|
|
426
|
+
updateConfigValue('apiToken', token)
|
|
427
|
+
}
|
|
428
|
+
} else {
|
|
429
|
+
fs.mkdirSync(path.dirname(configPath), {
|
|
430
|
+
recursive: true
|
|
431
|
+
})
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
return _cachedConfig
|
|
436
|
+
}
|
|
437
|
+
let _configPath
|
|
438
|
+
let _warnedConfigPathWin32Missing = false
|
|
439
|
+
function getConfigPath() {
|
|
440
|
+
// Get the OS app data folder:
|
|
441
|
+
// - Win: %LOCALAPPDATA% or fail?
|
|
442
|
+
// - Mac: %XDG_DATA_HOME% or fallback to "~/Library/Application Support/"
|
|
443
|
+
// - Linux: %XDG_DATA_HOME% or fallback to "~/.local/share/"
|
|
444
|
+
// Note: LOCALAPPDATA is typically: C:\Users\USERNAME\AppData
|
|
445
|
+
// Note: XDG stands for "X Desktop Group", nowadays "freedesktop.org"
|
|
446
|
+
// On most systems that path is: $HOME/.local/share
|
|
447
|
+
// Then append `socket/settings`, so:
|
|
448
|
+
// - Win: %LOCALAPPDATA%\socket\settings or return undefined
|
|
449
|
+
// - Mac: %XDG_DATA_HOME%/socket/settings or "~/Library/Application Support/socket/settings"
|
|
450
|
+
// - Linux: %XDG_DATA_HOME%/socket/settings or "~/.local/share/socket/settings"
|
|
451
|
+
|
|
452
|
+
if (_configPath === undefined) {
|
|
453
|
+
// Lazily access constants.WIN32.
|
|
454
|
+
const { WIN32 } = constants
|
|
455
|
+
let dataHome = WIN32
|
|
456
|
+
? // Lazily access constants.ENV.LOCALAPPDATA
|
|
457
|
+
constants.ENV.LOCALAPPDATA
|
|
458
|
+
: // Lazily access constants.ENV.XDG_DATA_HOME
|
|
459
|
+
constants.ENV.XDG_DATA_HOME
|
|
460
|
+
if (!dataHome) {
|
|
461
|
+
if (WIN32) {
|
|
462
|
+
if (!_warnedConfigPathWin32Missing) {
|
|
463
|
+
_warnedConfigPathWin32Missing = true
|
|
464
|
+
logger.logger.warn(`Missing %${LOCALAPPDATA}%`)
|
|
465
|
+
}
|
|
466
|
+
} else {
|
|
467
|
+
dataHome = path.join(
|
|
468
|
+
os.homedir(),
|
|
469
|
+
...(process.platform === 'darwin'
|
|
470
|
+
? ['Library', 'Application Support']
|
|
471
|
+
: ['.local', 'share'])
|
|
472
|
+
)
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
_configPath = dataHome ? path.join(dataHome, SOCKET_APP_DIR) : undefined
|
|
476
|
+
}
|
|
477
|
+
return _configPath
|
|
478
|
+
}
|
|
479
|
+
function normalizeConfigKey(key) {
|
|
480
|
+
// Note: apiKey was the old name of the token. When we load a config with
|
|
481
|
+
// property apiKey, we'll copy that to apiToken and delete the old property.
|
|
482
|
+
const normalizedKey = key === 'apiKey' ? 'apiToken' : key
|
|
483
|
+
if (!supportedConfigKeys.has(normalizedKey)) {
|
|
484
|
+
return {
|
|
485
|
+
ok: false,
|
|
486
|
+
message: `Invalid config key: ${normalizedKey}`,
|
|
487
|
+
data: undefined
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return {
|
|
491
|
+
ok: true,
|
|
492
|
+
data: key
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
function findSocketYmlSync(dir = process.cwd()) {
|
|
496
|
+
let prevDir = null
|
|
497
|
+
while (dir !== prevDir) {
|
|
498
|
+
let ymlPath = path.join(dir, 'socket.yml')
|
|
499
|
+
let yml = safeReadFileSync(ymlPath)
|
|
500
|
+
if (yml === undefined) {
|
|
501
|
+
ymlPath = path.join(dir, 'socket.yaml')
|
|
502
|
+
yml = safeReadFileSync(ymlPath)
|
|
503
|
+
}
|
|
504
|
+
if (typeof yml === 'string') {
|
|
505
|
+
try {
|
|
506
|
+
return {
|
|
507
|
+
path: ymlPath,
|
|
508
|
+
parsed: vendor.configExports.parseSocketConfig(yml)
|
|
509
|
+
}
|
|
510
|
+
} catch {
|
|
511
|
+
throw new Error(`Found file but was unable to parse ${ymlPath}`)
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
prevDir = dir
|
|
515
|
+
dir = path.join(dir, '..')
|
|
516
|
+
}
|
|
517
|
+
return null
|
|
518
|
+
}
|
|
519
|
+
function getConfigValue(key) {
|
|
520
|
+
const localConfig = getConfigValues()
|
|
521
|
+
const keyResult = normalizeConfigKey(key)
|
|
522
|
+
if (!keyResult.ok) {
|
|
523
|
+
return keyResult
|
|
524
|
+
}
|
|
525
|
+
return {
|
|
526
|
+
ok: true,
|
|
527
|
+
data: localConfig[keyResult.data]
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
// This version squashes errors, returning undefined instead.
|
|
531
|
+
// Should be used when we can reasonably predict the call can't fail.
|
|
532
|
+
function getConfigValueOrUndef(key) {
|
|
533
|
+
const localConfig = getConfigValues()
|
|
534
|
+
const keyResult = normalizeConfigKey(key)
|
|
535
|
+
if (!keyResult.ok) {
|
|
536
|
+
return undefined
|
|
537
|
+
}
|
|
538
|
+
return localConfig[keyResult.data]
|
|
539
|
+
}
|
|
540
|
+
function isReadOnlyConfig() {
|
|
541
|
+
return _readOnlyConfig
|
|
542
|
+
}
|
|
543
|
+
let _pendingSave = false
|
|
544
|
+
function updateConfigValue(key, value) {
|
|
545
|
+
const localConfig = getConfigValues()
|
|
546
|
+
const keyResult = normalizeConfigKey(key)
|
|
547
|
+
if (!keyResult.ok) {
|
|
548
|
+
return keyResult
|
|
549
|
+
}
|
|
550
|
+
localConfig[keyResult.data] = value
|
|
551
|
+
if (_readOnlyConfig) {
|
|
552
|
+
return {
|
|
553
|
+
ok: true,
|
|
554
|
+
message: `Config key '${key}' was updated`,
|
|
555
|
+
data: 'Change applied but not persisted; current config is overridden through env var or flag'
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
if (!_pendingSave) {
|
|
559
|
+
_pendingSave = true
|
|
560
|
+
process.nextTick(() => {
|
|
561
|
+
_pendingSave = false
|
|
562
|
+
const configPath = getConfigPath()
|
|
563
|
+
if (configPath) {
|
|
564
|
+
fs.writeFileSync(
|
|
565
|
+
configPath,
|
|
566
|
+
Buffer.from(JSON.stringify(localConfig)).toString('base64')
|
|
567
|
+
)
|
|
568
|
+
}
|
|
569
|
+
})
|
|
570
|
+
}
|
|
571
|
+
return {
|
|
572
|
+
ok: true,
|
|
573
|
+
message: `Config key '${key}' was updated`,
|
|
574
|
+
data: undefined
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
function isTestingV1() {
|
|
578
|
+
return !!getConfigValueOrUndef('isTestingV1')
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const {
|
|
582
|
+
kInternalsSymbol,
|
|
583
|
+
[kInternalsSymbol]: { getSentry }
|
|
584
|
+
} = constants
|
|
585
|
+
class AuthError extends Error {}
|
|
586
|
+
class InputError extends Error {
|
|
587
|
+
constructor(message, body) {
|
|
588
|
+
super(message)
|
|
589
|
+
this.body = body
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
async function captureException(exception, hint) {
|
|
593
|
+
const result = captureExceptionSync(exception, hint)
|
|
594
|
+
// "Sleep" for a second, just in case, hopefully enough time to initiate fetch.
|
|
595
|
+
await promises.setTimeout(1000)
|
|
596
|
+
return result
|
|
597
|
+
}
|
|
598
|
+
function captureExceptionSync(exception, hint) {
|
|
599
|
+
const Sentry = getSentry()
|
|
600
|
+
if (!Sentry) {
|
|
601
|
+
return ''
|
|
602
|
+
}
|
|
603
|
+
debug.debugLog('captureException: Sending exception to Sentry')
|
|
604
|
+
return Sentry.captureException(exception, hint)
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function failMsgWithBadge(badge, msg) {
|
|
608
|
+
return `${vendor.yoctocolorsCjsExports.bgRed(vendor.yoctocolorsCjsExports.bold(vendor.yoctocolorsCjsExports.white(` ${badge}${msg ? ': ' : ''}`)))}${msg ? ' ' + vendor.yoctocolorsCjsExports.bold(msg) : ''}`
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
const { SOCKET_PUBLIC_API_TOKEN } = constants
|
|
612
|
+
|
|
613
|
+
// The API server that should be used for operations.
|
|
614
|
+
function getDefaultApiBaseUrl$1() {
|
|
615
|
+
const baseUrl =
|
|
616
|
+
// Lazily access constants.ENV.SOCKET_SECURITY_API_BASE_URL.
|
|
617
|
+
constants.ENV.SOCKET_SECURITY_API_BASE_URL ||
|
|
618
|
+
getConfigValueOrUndef('apiBaseUrl')
|
|
619
|
+
return strings.isNonEmptyString(baseUrl) ? baseUrl : undefined
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// The API server that should be used for operations.
|
|
623
|
+
function getDefaultHttpProxy() {
|
|
624
|
+
const apiProxy =
|
|
625
|
+
// Lazily access constants.ENV.SOCKET_SECURITY_API_PROXY.
|
|
626
|
+
constants.ENV.SOCKET_SECURITY_API_PROXY || getConfigValueOrUndef('apiProxy')
|
|
627
|
+
return strings.isNonEmptyString(apiProxy) ? apiProxy : undefined
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// This API key should be stored globally for the duration of the CLI execution.
|
|
631
|
+
let _defaultToken
|
|
632
|
+
function getDefaultToken() {
|
|
633
|
+
// Lazily access constants.ENV.SOCKET_CLI_NO_API_TOKEN.
|
|
634
|
+
if (constants.ENV.SOCKET_CLI_NO_API_TOKEN) {
|
|
635
|
+
_defaultToken = undefined
|
|
636
|
+
} else {
|
|
637
|
+
const key =
|
|
638
|
+
// Lazily access constants.ENV.SOCKET_SECURITY_API_TOKEN.
|
|
639
|
+
constants.ENV.SOCKET_SECURITY_API_TOKEN ||
|
|
640
|
+
getConfigValueOrUndef('apiToken') ||
|
|
641
|
+
_defaultToken
|
|
642
|
+
_defaultToken = strings.isNonEmptyString(key) ? key : undefined
|
|
643
|
+
}
|
|
644
|
+
return _defaultToken
|
|
645
|
+
}
|
|
646
|
+
function getVisibleTokenPrefix() {
|
|
647
|
+
const apiToken = getDefaultToken()
|
|
648
|
+
if (!apiToken) {
|
|
649
|
+
return ''
|
|
650
|
+
}
|
|
651
|
+
const PREFIX = 'sktsec_'
|
|
652
|
+
return apiToken.slice(PREFIX.length, PREFIX.length + 5)
|
|
653
|
+
}
|
|
654
|
+
function hasDefaultToken() {
|
|
655
|
+
return !!getDefaultToken()
|
|
656
|
+
}
|
|
657
|
+
function getPublicToken() {
|
|
658
|
+
return (
|
|
659
|
+
// Lazily access constants.ENV.SOCKET_SECURITY_API_TOKEN.
|
|
660
|
+
(constants.ENV.SOCKET_SECURITY_API_TOKEN || getDefaultToken()) ??
|
|
661
|
+
SOCKET_PUBLIC_API_TOKEN
|
|
662
|
+
)
|
|
663
|
+
}
|
|
664
|
+
async function setupSdk(
|
|
665
|
+
apiToken = getDefaultToken(),
|
|
666
|
+
apiBaseUrl = getDefaultApiBaseUrl$1(),
|
|
667
|
+
proxy = getDefaultHttpProxy()
|
|
668
|
+
) {
|
|
669
|
+
if (typeof apiToken !== 'string' && vendor.isInteractiveExports()) {
|
|
670
|
+
apiToken = await prompts.password({
|
|
671
|
+
message:
|
|
672
|
+
'Enter your Socket.dev API key (not saved, use socket login to persist)'
|
|
673
|
+
})
|
|
674
|
+
_defaultToken = apiToken
|
|
675
|
+
}
|
|
676
|
+
if (!apiToken) {
|
|
677
|
+
return {
|
|
678
|
+
ok: false,
|
|
679
|
+
message: 'Auth Error',
|
|
680
|
+
cause: 'You need to provide an API Token. Run `socket login` first.'
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
return {
|
|
684
|
+
ok: true,
|
|
685
|
+
data: new vendor.distExports$2.SocketSdk(apiToken, {
|
|
686
|
+
agent: proxy
|
|
687
|
+
? new vendor.HttpsProxyAgent({
|
|
688
|
+
proxy
|
|
689
|
+
})
|
|
690
|
+
: undefined,
|
|
691
|
+
baseUrl: apiBaseUrl,
|
|
692
|
+
userAgent: vendor.distExports$2.createUserAgentFromPkgJson({
|
|
693
|
+
// Lazily access constants.ENV.INLINED_SOCKET_CLI_NAME.
|
|
694
|
+
name: constants.ENV.INLINED_SOCKET_CLI_NAME,
|
|
695
|
+
// Lazily access constants.ENV.INLINED_SOCKET_CLI_VERSION.
|
|
696
|
+
version: constants.ENV.INLINED_SOCKET_CLI_VERSION,
|
|
697
|
+
// Lazily access constants.ENV.INLINED_SOCKET_CLI_HOMEPAGE.
|
|
698
|
+
homepage: constants.ENV.INLINED_SOCKET_CLI_HOMEPAGE
|
|
699
|
+
})
|
|
700
|
+
})
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// TODO: this function is removed after v1.0.0
|
|
705
|
+
function handleUnsuccessfulApiResponse(_name, error, cause, status) {
|
|
706
|
+
const message = `${error || 'No error message returned'}${cause ? ` (reason: ${cause})` : ''}`
|
|
707
|
+
if (status === 401 || status === 403) {
|
|
708
|
+
// Lazily access constants.spinner.
|
|
709
|
+
const { spinner } = constants
|
|
710
|
+
spinner.stop()
|
|
711
|
+
throw new AuthError(message)
|
|
712
|
+
}
|
|
713
|
+
logger.logger.fail(failMsgWithBadge('Socket API returned an error', message))
|
|
714
|
+
// eslint-disable-next-line n/no-process-exit
|
|
715
|
+
process.exit(1)
|
|
716
|
+
}
|
|
717
|
+
async function handleApiCall(value, fetchingDesc) {
|
|
718
|
+
// Lazily access constants.spinner.
|
|
719
|
+
const { spinner } = constants
|
|
720
|
+
spinner.start(`Requesting ${fetchingDesc} from API...`)
|
|
721
|
+
let result
|
|
722
|
+
try {
|
|
723
|
+
result = await value
|
|
724
|
+
|
|
725
|
+
// TODO: info, not success (looks weird when response is non-200)
|
|
726
|
+
spinner.successAndStop(
|
|
727
|
+
`Received API response (after requesting ${fetchingDesc}).`
|
|
728
|
+
)
|
|
729
|
+
} catch (e) {
|
|
730
|
+
spinner.failAndStop(`An error was thrown while requesting ${fetchingDesc}`)
|
|
731
|
+
debug.debugLog(`handleApiCall(${fetchingDesc}) threw error:\n`, e)
|
|
732
|
+
const message = `${e || 'No error message returned'}`
|
|
733
|
+
const cause = `${e || 'No error message returned'}`
|
|
734
|
+
return {
|
|
735
|
+
ok: false,
|
|
736
|
+
message: 'Socket API returned an error',
|
|
737
|
+
cause: `${message}${cause ? ` ( Reason: ${cause} )` : ''}`
|
|
738
|
+
}
|
|
739
|
+
} finally {
|
|
740
|
+
spinner.stop()
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// Note: TS can't narrow down the type of result due to generics
|
|
744
|
+
if (result.success === false) {
|
|
745
|
+
const err = result
|
|
746
|
+
const message = `${err.error || 'No error message returned'}`
|
|
747
|
+
debug.debugLog(`handleApiCall(${fetchingDesc}) bad response:\n`, err)
|
|
748
|
+
return {
|
|
749
|
+
ok: false,
|
|
750
|
+
message: 'Socket API returned an error',
|
|
751
|
+
cause: `${message}${err.cause ? ` ( Reason: ${err.cause} )` : ''}`,
|
|
752
|
+
data: {
|
|
753
|
+
code: result.status
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
} else {
|
|
757
|
+
const ok = result
|
|
758
|
+
return {
|
|
759
|
+
ok: true,
|
|
760
|
+
data: ok.data
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
async function handleApiCallNoSpinner(value, description) {
|
|
765
|
+
let result
|
|
766
|
+
try {
|
|
767
|
+
result = await value
|
|
768
|
+
} catch (e) {
|
|
769
|
+
debug.debugLog(`handleApiCall(${description}) threw error:\n`, e)
|
|
770
|
+
const message = `${e || 'No error message returned'}`
|
|
771
|
+
const cause = `${e || 'No error message returned'}`
|
|
772
|
+
return {
|
|
773
|
+
ok: false,
|
|
774
|
+
message: 'Socket API returned an error',
|
|
775
|
+
cause: `${message}${cause ? ` ( Reason: ${cause} )` : ''}`
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// Note: TS can't narrow down the type of result due to generics
|
|
780
|
+
if (result.success === false) {
|
|
781
|
+
const err = result
|
|
782
|
+
const message = `${err.error || 'No error message returned'}`
|
|
783
|
+
debug.debugLog(`handleApiCall(${description}) bad response:\n`, err)
|
|
784
|
+
return {
|
|
785
|
+
ok: false,
|
|
786
|
+
message: 'Socket API returned an error',
|
|
787
|
+
cause: `${message}${err.cause ? ` ( Reason: ${err.cause} )` : ''}`,
|
|
788
|
+
data: {
|
|
789
|
+
code: result.status
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
} else {
|
|
793
|
+
const ok = result
|
|
794
|
+
return {
|
|
795
|
+
ok: true,
|
|
796
|
+
data: ok.data
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
async function getErrorMessageForHttpStatusCode(code) {
|
|
801
|
+
if (code === 400) {
|
|
802
|
+
return 'One of the options passed might be incorrect'
|
|
803
|
+
}
|
|
804
|
+
if (code === 403 || code === 401) {
|
|
805
|
+
return 'Your API token may not have the required permissions for this command or you might be trying to access (data from) an organization that is not linked to the API key you are logged in with'
|
|
806
|
+
}
|
|
807
|
+
if (code === 404) {
|
|
808
|
+
return 'The requested Socket API endpoint was not found (404) or there was no result for the requested parameters. If unexpected, this could be a temporary problem caused by an incident or a bug in the CLI. If the problem persists please let us know.'
|
|
809
|
+
}
|
|
810
|
+
if (code === 500) {
|
|
811
|
+
return 'There was an unknown server side problem with your request. This ought to be temporary. Please let us know if this problem persists.'
|
|
812
|
+
}
|
|
813
|
+
return `Server responded with status code ${code}`
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// The API server that should be used for operations.
|
|
817
|
+
function getDefaultApiBaseUrl() {
|
|
818
|
+
// Lazily access constants.ENV.SOCKET_SECURITY_API_BASE_URL.
|
|
819
|
+
const SOCKET_SECURITY_API_BASE_URL =
|
|
820
|
+
constants.ENV.SOCKET_SECURITY_API_BASE_URL
|
|
821
|
+
const baseUrl =
|
|
822
|
+
SOCKET_SECURITY_API_BASE_URL || getConfigValueOrUndef('apiBaseUrl')
|
|
823
|
+
if (strings.isNonEmptyString(baseUrl)) {
|
|
824
|
+
return baseUrl
|
|
825
|
+
}
|
|
826
|
+
// Lazily access constants.API_V0_URL.
|
|
827
|
+
const API_V0_URL = constants.API_V0_URL
|
|
828
|
+
return API_V0_URL
|
|
829
|
+
}
|
|
830
|
+
async function queryApi(path, apiToken) {
|
|
831
|
+
const baseUrl = getDefaultApiBaseUrl() || ''
|
|
832
|
+
if (!baseUrl) {
|
|
833
|
+
logger.logger.warn(
|
|
834
|
+
'API endpoint is not set and default was empty. Request is likely to fail.'
|
|
835
|
+
)
|
|
836
|
+
}
|
|
837
|
+
return await fetch(`${baseUrl}${baseUrl.endsWith('/') ? '' : '/'}${path}`, {
|
|
838
|
+
method: 'GET',
|
|
839
|
+
headers: {
|
|
840
|
+
Authorization: `Basic ${btoa(`${apiToken}:`)}`
|
|
841
|
+
}
|
|
842
|
+
})
|
|
843
|
+
}
|
|
844
|
+
async function queryApiSafeText(path, fetchSpinnerDesc) {
|
|
845
|
+
const apiToken = getDefaultToken()
|
|
846
|
+
if (!apiToken) {
|
|
847
|
+
return {
|
|
848
|
+
ok: false,
|
|
849
|
+
message: 'Authentication Error',
|
|
850
|
+
cause:
|
|
851
|
+
'User must be authenticated to run this command. To log in, run the command `socket login` and enter your API key.'
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
if (fetchSpinnerDesc) {
|
|
855
|
+
// Lazily access constants.spinner.
|
|
856
|
+
const { spinner } = constants
|
|
857
|
+
spinner.start(`Requesting ${fetchSpinnerDesc} from API...`)
|
|
858
|
+
}
|
|
859
|
+
let result
|
|
860
|
+
try {
|
|
861
|
+
result = await queryApi(path, apiToken)
|
|
862
|
+
if (fetchSpinnerDesc) {
|
|
863
|
+
// Lazily access constants.spinner.
|
|
864
|
+
const { spinner } = constants
|
|
865
|
+
spinner.successAndStop(
|
|
866
|
+
`Received API response (after requesting ${fetchSpinnerDesc}).`
|
|
867
|
+
)
|
|
868
|
+
}
|
|
869
|
+
} catch (e) {
|
|
870
|
+
if (fetchSpinnerDesc) {
|
|
871
|
+
// Lazily access constants.spinner.
|
|
872
|
+
const { spinner } = constants
|
|
873
|
+
spinner.failAndStop(
|
|
874
|
+
`An error was thrown while requesting ${fetchSpinnerDesc}`
|
|
875
|
+
)
|
|
876
|
+
}
|
|
877
|
+
debug.debugLog('Error thrown trying to await queryApi():')
|
|
878
|
+
debug.debugLog(e)
|
|
879
|
+
const msg = e?.message
|
|
880
|
+
return {
|
|
881
|
+
ok: false,
|
|
882
|
+
message: 'API Request failed to complete',
|
|
883
|
+
...(msg
|
|
884
|
+
? {
|
|
885
|
+
cause: msg
|
|
886
|
+
}
|
|
887
|
+
: {})
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
if (!result.ok) {
|
|
891
|
+
const cause = await getErrorMessageForHttpStatusCode(result.status)
|
|
892
|
+
return {
|
|
893
|
+
ok: false,
|
|
894
|
+
message: 'Socket API returned an error',
|
|
895
|
+
cause: `${result.statusText}${cause ? ` (cause: ${cause})` : ''}`
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
try {
|
|
899
|
+
const data = await result.text()
|
|
900
|
+
return {
|
|
901
|
+
ok: true,
|
|
902
|
+
data
|
|
903
|
+
}
|
|
904
|
+
} catch (e) {
|
|
905
|
+
debug.debugLog('Error thrown trying to await result.text():')
|
|
906
|
+
debug.debugLog(e)
|
|
907
|
+
return {
|
|
908
|
+
ok: false,
|
|
909
|
+
message: 'API Request failed to complete',
|
|
910
|
+
cause: 'There was an unexpected error trying to read the response text'
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
async function queryApiSafeJson(path, fetchSpinnerDesc = '') {
|
|
915
|
+
const result = await queryApiSafeText(path, fetchSpinnerDesc)
|
|
916
|
+
if (!result.ok) {
|
|
917
|
+
return result
|
|
918
|
+
}
|
|
919
|
+
try {
|
|
920
|
+
return {
|
|
921
|
+
ok: true,
|
|
922
|
+
data: JSON.parse(result.data)
|
|
923
|
+
}
|
|
924
|
+
} catch (e) {
|
|
925
|
+
return {
|
|
926
|
+
ok: false,
|
|
927
|
+
message: 'Server returned invalid JSON',
|
|
928
|
+
cause: `Please report this. JSON.parse threw an error over the following response: \`${(result.data?.slice?.(0, 100) || '<empty>').trim() + (result.data?.length > 100 ? '...' : '')}\``
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
function mdTableStringNumber(title1, title2, obj) {
|
|
934
|
+
// | Date | Counts |
|
|
935
|
+
// | ----------- | ------ |
|
|
936
|
+
// | Header | 201464 |
|
|
937
|
+
// | Paragraph | 18 |
|
|
938
|
+
let mw1 = title1.length
|
|
939
|
+
let mw2 = title2.length
|
|
940
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
941
|
+
mw1 = Math.max(mw1, key.length)
|
|
942
|
+
mw2 = Math.max(mw2, String(value ?? '').length)
|
|
943
|
+
}
|
|
944
|
+
const lines = []
|
|
945
|
+
lines.push(`| ${title1.padEnd(mw1, ' ')} | ${title2.padEnd(mw2)} |`)
|
|
946
|
+
lines.push(`| ${'-'.repeat(mw1)} | ${'-'.repeat(mw2)} |`)
|
|
947
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
948
|
+
lines.push(
|
|
949
|
+
`| ${key.padEnd(mw1, ' ')} | ${String(value ?? '').padStart(mw2, ' ')} |`
|
|
950
|
+
)
|
|
951
|
+
}
|
|
952
|
+
lines.push(`| ${'-'.repeat(mw1)} | ${'-'.repeat(mw2)} |`)
|
|
953
|
+
return lines.join('\n')
|
|
954
|
+
}
|
|
955
|
+
function mdTable(
|
|
956
|
+
logs,
|
|
957
|
+
// This is saying "an array of strings and the strings are a valid key of elements of T"
|
|
958
|
+
// In turn, T is defined above as the audit log event type from our OpenAPI docs.
|
|
959
|
+
cols,
|
|
960
|
+
titles = cols
|
|
961
|
+
) {
|
|
962
|
+
// Max col width required to fit all data in that column
|
|
963
|
+
const cws = cols.map(col => col.length)
|
|
964
|
+
for (const log of logs) {
|
|
965
|
+
for (let i = 0, { length } = cols; i < length; i += 1) {
|
|
966
|
+
// @ts-ignore
|
|
967
|
+
const val = log[cols[i] ?? ''] ?? ''
|
|
968
|
+
cws[i] = Math.max(
|
|
969
|
+
cws[i] ?? 0,
|
|
970
|
+
String(val).length,
|
|
971
|
+
(titles[i] || '').length
|
|
972
|
+
)
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
let div = '|'
|
|
976
|
+
for (const cw of cws) {
|
|
977
|
+
div += ' ' + '-'.repeat(cw) + ' |'
|
|
978
|
+
}
|
|
979
|
+
let header = '|'
|
|
980
|
+
for (let i = 0, { length } = titles; i < length; i += 1) {
|
|
981
|
+
header += ' ' + String(titles[i]).padEnd(cws[i] ?? 0, ' ') + ' |'
|
|
982
|
+
}
|
|
983
|
+
let body = ''
|
|
984
|
+
for (const log of logs) {
|
|
985
|
+
body += '|'
|
|
986
|
+
for (let i = 0, { length } = cols; i < length; i += 1) {
|
|
987
|
+
// @ts-ignore
|
|
988
|
+
const val = log[cols[i] ?? ''] ?? ''
|
|
989
|
+
body += ' ' + String(val).padEnd(cws[i] ?? 0, ' ') + ' |'
|
|
990
|
+
}
|
|
991
|
+
body += '\n'
|
|
992
|
+
}
|
|
993
|
+
return [div, header, div, body.trim(), div].filter(s => !!s.trim()).join('\n')
|
|
994
|
+
}
|
|
995
|
+
function mdTableOfPairs(
|
|
996
|
+
arr,
|
|
997
|
+
// This is saying "an array of strings and the strings are a valid key of elements of T"
|
|
998
|
+
// In turn, T is defined above as the audit log event type from our OpenAPI docs.
|
|
999
|
+
cols
|
|
1000
|
+
) {
|
|
1001
|
+
// Max col width required to fit all data in that column
|
|
1002
|
+
const cws = cols.map(col => col.length)
|
|
1003
|
+
for (const [key, val] of arr) {
|
|
1004
|
+
cws[0] = Math.max(cws[0] ?? 0, String(key).length)
|
|
1005
|
+
cws[1] = Math.max(cws[1] ?? 0, String(val ?? '').length)
|
|
1006
|
+
}
|
|
1007
|
+
let div = '|'
|
|
1008
|
+
for (const cw of cws) {
|
|
1009
|
+
div += ' ' + '-'.repeat(cw) + ' |'
|
|
1010
|
+
}
|
|
1011
|
+
let header = '|'
|
|
1012
|
+
for (let i = 0, { length } = cols; i < length; i += 1) {
|
|
1013
|
+
header += ' ' + String(cols[i]).padEnd(cws[i] ?? 0, ' ') + ' |'
|
|
1014
|
+
}
|
|
1015
|
+
let body = ''
|
|
1016
|
+
for (const [key, val] of arr) {
|
|
1017
|
+
body += '|'
|
|
1018
|
+
body += ' ' + String(key).padEnd(cws[0] ?? 0, ' ') + ' |'
|
|
1019
|
+
body += ' ' + String(val ?? '').padEnd(cws[1] ?? 0, ' ') + ' |'
|
|
1020
|
+
body += '\n'
|
|
1021
|
+
}
|
|
1022
|
+
return [div, header, div, body.trim(), div].filter(s => !!s.trim()).join('\n')
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
// Serialize the final result object before printing it
|
|
1026
|
+
// All commands that support the --json flag should call this before printing
|
|
1027
|
+
function serializeResultJson(data) {
|
|
1028
|
+
if (typeof data !== 'object' || !data) {
|
|
1029
|
+
process.exitCode = 1
|
|
1030
|
+
// We should not allow to expect the json value to be "null", or a boolean/number/string, even if they are valid "json".
|
|
1031
|
+
const msg =
|
|
1032
|
+
'There was a problem converting the data set to JSON. The JSON was not an object. Please try again without --json'
|
|
1033
|
+
debug.debugLog('typeof data=', typeof data)
|
|
1034
|
+
if (typeof data !== 'object' && data) {
|
|
1035
|
+
debug.debugLog('data:\n', data)
|
|
1036
|
+
}
|
|
1037
|
+
return (
|
|
1038
|
+
JSON.stringify({
|
|
1039
|
+
ok: false,
|
|
1040
|
+
message: 'Unable to serialize JSON',
|
|
1041
|
+
data: msg
|
|
1042
|
+
}).trim() + '\n'
|
|
1043
|
+
)
|
|
1044
|
+
}
|
|
1045
|
+
try {
|
|
1046
|
+
return JSON.stringify(data, null, 2).trim() + '\n'
|
|
1047
|
+
} catch (e) {
|
|
1048
|
+
debug.debugLog('Error:\n', e)
|
|
1049
|
+
process.exitCode = 1
|
|
1050
|
+
// This could be caused by circular references, which is an "us" problem
|
|
1051
|
+
const msg =
|
|
1052
|
+
'There was a problem converting the data set to JSON. Please try again without --json'
|
|
1053
|
+
logger.logger.error(msg)
|
|
1054
|
+
return (
|
|
1055
|
+
JSON.stringify({
|
|
1056
|
+
ok: false,
|
|
1057
|
+
message: 'Unable to serialize JSON',
|
|
1058
|
+
data: msg
|
|
1059
|
+
}).trim() + '\n'
|
|
1060
|
+
)
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
// TODO: not sure if I'm missing something but meow doesn't seem to expose this?
|
|
1065
|
+
|
|
1066
|
+
// Note: we use this description in getFlagListOutput, meow doesn't care
|
|
1067
|
+
|
|
1068
|
+
const commonFlags = {
|
|
1069
|
+
config: {
|
|
1070
|
+
type: 'string',
|
|
1071
|
+
default: '',
|
|
1072
|
+
hidden: true,
|
|
1073
|
+
description: 'Override the local config with this JSON'
|
|
1074
|
+
},
|
|
1075
|
+
dryRun: {
|
|
1076
|
+
type: 'boolean',
|
|
1077
|
+
default: false,
|
|
1078
|
+
hidden: true,
|
|
1079
|
+
// Only show in root command
|
|
1080
|
+
description: 'Do input validation for a command and exit 0 when input is ok'
|
|
1081
|
+
},
|
|
1082
|
+
help: {
|
|
1083
|
+
type: 'boolean',
|
|
1084
|
+
default: false,
|
|
1085
|
+
shortFlag: 'h',
|
|
1086
|
+
description: 'Print this help'
|
|
1087
|
+
},
|
|
1088
|
+
silent: {
|
|
1089
|
+
type: 'boolean',
|
|
1090
|
+
default: false,
|
|
1091
|
+
hidden: true,
|
|
1092
|
+
shortFlag: 's',
|
|
1093
|
+
description: 'Make the CLI less chatty'
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
const outputFlags = {
|
|
1097
|
+
json: {
|
|
1098
|
+
type: 'boolean',
|
|
1099
|
+
shortFlag: 'j',
|
|
1100
|
+
default: false,
|
|
1101
|
+
description: 'Output result as json'
|
|
1102
|
+
},
|
|
1103
|
+
markdown: {
|
|
1104
|
+
type: 'boolean',
|
|
1105
|
+
shortFlag: 'm',
|
|
1106
|
+
default: false,
|
|
1107
|
+
description: 'Output result as markdown'
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
const validationFlags = {
|
|
1111
|
+
all: {
|
|
1112
|
+
type: 'boolean',
|
|
1113
|
+
default: false,
|
|
1114
|
+
description: 'Include all issues'
|
|
1115
|
+
},
|
|
1116
|
+
strict: {
|
|
1117
|
+
type: 'boolean',
|
|
1118
|
+
default: false,
|
|
1119
|
+
description: 'Exits with an error code if any matching issues are found'
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
function checkCommandInput(outputKind, ...checks) {
|
|
1124
|
+
if (checks.every(d => d.test)) {
|
|
1125
|
+
return true
|
|
1126
|
+
}
|
|
1127
|
+
const msg = ['Please review the input requirements and try again', '']
|
|
1128
|
+
for (const d of checks) {
|
|
1129
|
+
// If nook, then ignore when test is ok
|
|
1130
|
+
if (d.nook && d.test) {
|
|
1131
|
+
continue
|
|
1132
|
+
}
|
|
1133
|
+
const lines = d.message.split('\n')
|
|
1134
|
+
const { length: lineCount } = lines
|
|
1135
|
+
if (!lineCount) {
|
|
1136
|
+
continue
|
|
1137
|
+
}
|
|
1138
|
+
// If the message has newlines then format the first line with the input
|
|
1139
|
+
// expectation and the rest indented below it.
|
|
1140
|
+
msg.push(
|
|
1141
|
+
` - ${lines[0]} (${d.test ? vendor.yoctocolorsCjsExports.green(d.pass) : vendor.yoctocolorsCjsExports.red(d.fail)})`
|
|
1142
|
+
)
|
|
1143
|
+
if (lineCount > 1) {
|
|
1144
|
+
msg.push(...lines.slice(1).map(str => ` ${str}`))
|
|
1145
|
+
}
|
|
1146
|
+
msg.push('')
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
// Use exit status of 2 to indicate incorrect usage, generally invalid
|
|
1150
|
+
// options or missing arguments.
|
|
1151
|
+
// https://www.gnu.org/software/bash/manual/html_node/Exit-Status.html
|
|
1152
|
+
process.exitCode = 2
|
|
1153
|
+
if (outputKind === 'json') {
|
|
1154
|
+
logger.logger.log(
|
|
1155
|
+
serializeResultJson({
|
|
1156
|
+
ok: false,
|
|
1157
|
+
message: 'Input error',
|
|
1158
|
+
data: msg.join('\n')
|
|
1159
|
+
})
|
|
1160
|
+
)
|
|
1161
|
+
} else {
|
|
1162
|
+
logger.logger.fail(failMsgWithBadge('Input error', msg.join('\n')))
|
|
1163
|
+
}
|
|
1164
|
+
return false
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
function getOutputKind(json, markdown) {
|
|
1168
|
+
if (json) {
|
|
1169
|
+
return 'json'
|
|
1170
|
+
}
|
|
1171
|
+
if (markdown) {
|
|
1172
|
+
return 'markdown'
|
|
1173
|
+
}
|
|
1174
|
+
return 'text'
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
function getFlagListOutput(list, indent, { keyPrefix = '--', padName } = {}) {
|
|
1178
|
+
return getHelpListOutput(
|
|
1179
|
+
{
|
|
1180
|
+
...list
|
|
1181
|
+
},
|
|
1182
|
+
indent,
|
|
1183
|
+
{
|
|
1184
|
+
keyPrefix,
|
|
1185
|
+
padName
|
|
1186
|
+
}
|
|
1187
|
+
)
|
|
1188
|
+
}
|
|
1189
|
+
function getHelpListOutput(
|
|
1190
|
+
list,
|
|
1191
|
+
indent,
|
|
1192
|
+
{ keyPrefix = '', padName = 18 } = {}
|
|
1193
|
+
) {
|
|
1194
|
+
let result = ''
|
|
1195
|
+
const names = Object.keys(list).sort()
|
|
1196
|
+
for (const name of names) {
|
|
1197
|
+
const entry = list[name]
|
|
1198
|
+
if (entry && 'hidden' in entry && entry?.hidden) {
|
|
1199
|
+
continue
|
|
1200
|
+
}
|
|
1201
|
+
const description =
|
|
1202
|
+
(typeof entry === 'object' ? entry.description : entry) || ''
|
|
1203
|
+
result +=
|
|
1204
|
+
''.padEnd(indent) +
|
|
1205
|
+
(keyPrefix + name).padEnd(padName) +
|
|
1206
|
+
description +
|
|
1207
|
+
'\n'
|
|
1208
|
+
}
|
|
1209
|
+
return result.trim() || '(none)'
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
async function meowWithSubcommands(subcommands, options) {
|
|
1213
|
+
const {
|
|
1214
|
+
aliases = {},
|
|
1215
|
+
argv,
|
|
1216
|
+
defaultSub,
|
|
1217
|
+
importMeta,
|
|
1218
|
+
name,
|
|
1219
|
+
...additionalOptions
|
|
1220
|
+
} = {
|
|
1221
|
+
__proto__: null,
|
|
1222
|
+
...options
|
|
1223
|
+
}
|
|
1224
|
+
const [commandOrAliasName_, ...rawCommandArgv] = argv
|
|
1225
|
+
let commandOrAliasName = commandOrAliasName_
|
|
1226
|
+
if (!commandOrAliasName && defaultSub) {
|
|
1227
|
+
commandOrAliasName = defaultSub
|
|
1228
|
+
}
|
|
1229
|
+
const flags = {
|
|
1230
|
+
...commonFlags,
|
|
1231
|
+
...additionalOptions.flags
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
// No further args or first arg is a flag (shrug)
|
|
1235
|
+
if (
|
|
1236
|
+
name === 'socket' &&
|
|
1237
|
+
(!commandOrAliasName || commandOrAliasName?.startsWith('-'))
|
|
1238
|
+
) {
|
|
1239
|
+
flags['dryRun'] = {
|
|
1240
|
+
type: 'boolean',
|
|
1241
|
+
default: false,
|
|
1242
|
+
hidden: false,
|
|
1243
|
+
// Only show on root
|
|
1244
|
+
description:
|
|
1245
|
+
'Do input validation for a command and exit 0 when input is ok. Every command should support this flag (not shown on help screens)'
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
// This is basically a dry-run parse of cli args and flags. We use this to
|
|
1250
|
+
// determine config overrides and expected output mode.
|
|
1251
|
+
const cli1 = vendor.meow(`(this should never be printed)`, {
|
|
1252
|
+
argv,
|
|
1253
|
+
importMeta,
|
|
1254
|
+
...additionalOptions,
|
|
1255
|
+
flags,
|
|
1256
|
+
// Do not strictly check for flags here.
|
|
1257
|
+
allowUnknownFlags: true,
|
|
1258
|
+
// We will emit help when we're ready
|
|
1259
|
+
// Plus, if we allow this then meow() can just exit here.
|
|
1260
|
+
autoHelp: false
|
|
1261
|
+
})
|
|
1262
|
+
|
|
1263
|
+
// Hard override the config if instructed to do so.
|
|
1264
|
+
// The env var overrides the --flag, which overrides the persisted config
|
|
1265
|
+
// Also, when either of these are used, config updates won't persist.
|
|
1266
|
+
let configOverrideResult
|
|
1267
|
+
// Lazily access constants.ENV.SOCKET_CLI_CONFIG.
|
|
1268
|
+
if (constants.ENV.SOCKET_CLI_CONFIG) {
|
|
1269
|
+
configOverrideResult = overrideCachedConfig(
|
|
1270
|
+
// Lazily access constants.ENV.SOCKET_CLI_CONFIG.
|
|
1271
|
+
constants.ENV.SOCKET_CLI_CONFIG
|
|
1272
|
+
)
|
|
1273
|
+
} else if (cli1.flags['config']) {
|
|
1274
|
+
configOverrideResult = overrideCachedConfig(
|
|
1275
|
+
String(cli1.flags['config'] || '')
|
|
1276
|
+
)
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
// Lazily access constants.ENV.SOCKET_CLI_NO_API_TOKEN.
|
|
1280
|
+
if (constants.ENV.SOCKET_CLI_NO_API_TOKEN) {
|
|
1281
|
+
// This overrides the config override and even the explicit token env var.
|
|
1282
|
+
// The config will be marked as readOnly to prevent persisting it.
|
|
1283
|
+
overrideConfigApiToken(undefined)
|
|
1284
|
+
} else {
|
|
1285
|
+
// Lazily access constants.ENV.SOCKET_SECURITY_API_TOKEN.
|
|
1286
|
+
const tokenOverride = constants.ENV.SOCKET_SECURITY_API_TOKEN
|
|
1287
|
+
if (tokenOverride) {
|
|
1288
|
+
// This will set the token (even if there was a config override) and
|
|
1289
|
+
// set it to readOnly, making sure the temp token won't be persisted.
|
|
1290
|
+
overrideConfigApiToken(tokenOverride)
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
if (configOverrideResult?.ok === false) {
|
|
1294
|
+
emitBanner(name)
|
|
1295
|
+
logger.logger.fail(configOverrideResult.message)
|
|
1296
|
+
process.exitCode = 2
|
|
1297
|
+
return
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
// If we got at least some args, then lets find out if we can find a command.
|
|
1301
|
+
if (commandOrAliasName) {
|
|
1302
|
+
const alias = aliases[commandOrAliasName]
|
|
1303
|
+
// First: Resolve argv data from alias if its an alias that's been given.
|
|
1304
|
+
const [commandName, ...commandArgv] = alias
|
|
1305
|
+
? [...alias.argv, ...rawCommandArgv]
|
|
1306
|
+
: [commandOrAliasName, ...rawCommandArgv]
|
|
1307
|
+
// Second: Find a command definition using that data.
|
|
1308
|
+
const commandDefinition = commandName ? subcommands[commandName] : undefined
|
|
1309
|
+
// Third: If a valid command has been found, then we run it...
|
|
1310
|
+
if (commandDefinition) {
|
|
1311
|
+
return await commandDefinition.run(commandArgv, importMeta, {
|
|
1312
|
+
parentName: name
|
|
1313
|
+
})
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
if (isTestingV1()) {
|
|
1317
|
+
delete subcommands['diff-scan']
|
|
1318
|
+
delete subcommands['info']
|
|
1319
|
+
delete subcommands['report']
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
// Parse it again. Config overrides should now be applied (may affect help).
|
|
1323
|
+
const cli2 = vendor.meow(
|
|
1324
|
+
`
|
|
1325
|
+
Usage
|
|
1326
|
+
$ ${name} <command>
|
|
1327
|
+
|
|
1328
|
+
Commands
|
|
1329
|
+
${getHelpListOutput(
|
|
1330
|
+
{
|
|
1331
|
+
...objects.toSortedObject(
|
|
1332
|
+
Object.fromEntries(
|
|
1333
|
+
Object.entries(subcommands).filter(
|
|
1334
|
+
({ 1: subcommand }) => !subcommand.hidden
|
|
1335
|
+
)
|
|
1336
|
+
)
|
|
1337
|
+
),
|
|
1338
|
+
...objects.toSortedObject(
|
|
1339
|
+
Object.fromEntries(
|
|
1340
|
+
Object.entries(aliases).filter(({ 1: alias }) => {
|
|
1341
|
+
const { hidden } = alias
|
|
1342
|
+
const cmdName = hidden ? '' : alias.argv[0]
|
|
1343
|
+
const subcommand = cmdName ? subcommands[cmdName] : undefined
|
|
1344
|
+
return subcommand && !subcommand.hidden
|
|
1345
|
+
})
|
|
1346
|
+
)
|
|
1347
|
+
)
|
|
1348
|
+
},
|
|
1349
|
+
6
|
|
1350
|
+
)}
|
|
1351
|
+
|
|
1352
|
+
Options
|
|
1353
|
+
${getFlagListOutput(flags, 6)}
|
|
1354
|
+
|
|
1355
|
+
Examples
|
|
1356
|
+
$ ${name} --help
|
|
1357
|
+
`,
|
|
1358
|
+
{
|
|
1359
|
+
argv,
|
|
1360
|
+
importMeta,
|
|
1361
|
+
...additionalOptions,
|
|
1362
|
+
flags,
|
|
1363
|
+
// Do not strictly check for flags here.
|
|
1364
|
+
allowUnknownFlags: true,
|
|
1365
|
+
// We will emit help when we're ready
|
|
1366
|
+
// Plus, if we allow this then meow() can just exit here.
|
|
1367
|
+
autoHelp: false
|
|
1368
|
+
}
|
|
1369
|
+
)
|
|
1370
|
+
|
|
1371
|
+
// ...else we provide basic instructions and help.
|
|
1372
|
+
if (!cli2.flags['silent']) {
|
|
1373
|
+
emitBanner(name)
|
|
1374
|
+
}
|
|
1375
|
+
if (!cli2.flags['help'] && cli2.flags['dryRun']) {
|
|
1376
|
+
process.exitCode = 0
|
|
1377
|
+
// Lazily access constants.DRY_RUN_LABEL.
|
|
1378
|
+
logger.logger.log(
|
|
1379
|
+
`${constants.DRY_RUN_LABEL}: No-op, call a sub-command; ok`
|
|
1380
|
+
)
|
|
1381
|
+
} else {
|
|
1382
|
+
// When you explicitly request --help, the command should be successful
|
|
1383
|
+
// so we exit(0). If we do it because we need more input, we exit(2).
|
|
1384
|
+
cli2.showHelp(cli2.flags['help'] ? 0 : 2)
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
/**
|
|
1389
|
+
* Note: meow will exit immediately if it calls its .showHelp()
|
|
1390
|
+
*/
|
|
1391
|
+
function meowOrExit({
|
|
1392
|
+
allowUnknownFlags,
|
|
1393
|
+
// commands that pass-through args need to allow this
|
|
1394
|
+
argv,
|
|
1395
|
+
config,
|
|
1396
|
+
importMeta,
|
|
1397
|
+
parentName
|
|
1398
|
+
}) {
|
|
1399
|
+
const command = `${parentName} ${config.commandName}`
|
|
1400
|
+
|
|
1401
|
+
// This exits if .printHelp() is called either by meow itself or by us.
|
|
1402
|
+
const cli = vendor.meow({
|
|
1403
|
+
argv,
|
|
1404
|
+
description: config.description,
|
|
1405
|
+
help: config.help(command, config),
|
|
1406
|
+
importMeta,
|
|
1407
|
+
flags: config.flags,
|
|
1408
|
+
allowUnknownFlags: true,
|
|
1409
|
+
// meow will exit(1) before printing the banner
|
|
1410
|
+
autoHelp: false // meow will exit(0) before printing the banner
|
|
1411
|
+
})
|
|
1412
|
+
if (!cli.flags['silent']) {
|
|
1413
|
+
emitBanner(command)
|
|
1414
|
+
}
|
|
1415
|
+
if (!allowUnknownFlags) {
|
|
1416
|
+
// Run meow specifically with the flag setting. It will exit(2) if an
|
|
1417
|
+
// invalid flag is set and print a message.
|
|
1418
|
+
vendor.meow({
|
|
1419
|
+
argv,
|
|
1420
|
+
description: config.description,
|
|
1421
|
+
help: config.help(command, config),
|
|
1422
|
+
importMeta,
|
|
1423
|
+
flags: config.flags,
|
|
1424
|
+
allowUnknownFlags: false,
|
|
1425
|
+
autoHelp: false
|
|
1426
|
+
})
|
|
1427
|
+
}
|
|
1428
|
+
if (cli.flags['help']) {
|
|
1429
|
+
cli.showHelp(0)
|
|
1430
|
+
}
|
|
1431
|
+
// Now test for help state. Run meow again. If it exits now, it must be due
|
|
1432
|
+
// to wanting to print the help screen. But it would exit(0) and we want a
|
|
1433
|
+
// consistent exit(2) for that case (missing input). TODO: move away from meow
|
|
1434
|
+
process.exitCode = 2
|
|
1435
|
+
vendor.meow({
|
|
1436
|
+
argv,
|
|
1437
|
+
description: config.description,
|
|
1438
|
+
help: config.help(command, config),
|
|
1439
|
+
importMeta,
|
|
1440
|
+
flags: config.flags,
|
|
1441
|
+
allowUnknownFlags: Boolean(allowUnknownFlags),
|
|
1442
|
+
autoHelp: false
|
|
1443
|
+
})
|
|
1444
|
+
// Ok, no help, reset to default.
|
|
1445
|
+
process.exitCode = 0
|
|
1446
|
+
return cli
|
|
1447
|
+
}
|
|
1448
|
+
function emitBanner(name) {
|
|
1449
|
+
// Print a banner at the top of each command.
|
|
1450
|
+
// This helps with brand recognition and marketing.
|
|
1451
|
+
// It also helps with debugging since it contains version and command details.
|
|
1452
|
+
// Note: print over stderr to preserve stdout for flags like --json and
|
|
1453
|
+
// --markdown. If we don't do this, you can't use --json in particular
|
|
1454
|
+
// and pipe the result to other tools. By emitting the banner over stderr
|
|
1455
|
+
// you can do something like `socket scan view xyz | jq | process`.
|
|
1456
|
+
// The spinner also emits over stderr for example.
|
|
1457
|
+
logger.logger.error(getAsciiHeader(name))
|
|
1458
|
+
}
|
|
1459
|
+
function getAsciiHeader(command) {
|
|
1460
|
+
// Note: In tests we return <redacted> because otherwise snapshots will fail.
|
|
1461
|
+
const { REDACTED } = constants
|
|
1462
|
+
// Lazily access constants.ENV.VITEST.
|
|
1463
|
+
const redacting = constants.ENV.VITEST
|
|
1464
|
+
const cliVersion = redacting
|
|
1465
|
+
? REDACTED
|
|
1466
|
+
: // Lazily access constants.ENV.INLINED_SOCKET_CLI_VERSION_HASH.
|
|
1467
|
+
constants.ENV.INLINED_SOCKET_CLI_VERSION_HASH
|
|
1468
|
+
const nodeVersion = redacting ? REDACTED : process.version
|
|
1469
|
+
const defaultOrg = getConfigValueOrUndef('defaultOrg')
|
|
1470
|
+
const readOnlyConfig = isReadOnlyConfig() ? '*' : '.'
|
|
1471
|
+
const v1test = isTestingV1() ? ' (is testing v1)' : ''
|
|
1472
|
+
const feedback = isTestingV1()
|
|
1473
|
+
? vendor.yoctocolorsCjsExports.green(
|
|
1474
|
+
' (Thank you for testing the v1 bump! Please send us any feedback you might have!)\n'
|
|
1475
|
+
)
|
|
1476
|
+
: ''
|
|
1477
|
+
const shownToken = redacting ? REDACTED : getVisibleTokenPrefix() || 'no'
|
|
1478
|
+
const relCwd = redacting
|
|
1479
|
+
? REDACTED
|
|
1480
|
+
: path$1.normalizePath(
|
|
1481
|
+
process
|
|
1482
|
+
.cwd()
|
|
1483
|
+
.replace(
|
|
1484
|
+
new RegExp(
|
|
1485
|
+
`^${regexps.escapeRegExp(constants.homePath)}(?:${path.sep}|$)`,
|
|
1486
|
+
'i'
|
|
1487
|
+
),
|
|
1488
|
+
'~/'
|
|
1489
|
+
)
|
|
1490
|
+
)
|
|
1491
|
+
let nodeVerWarn = ''
|
|
1492
|
+
if ((vendor.semverExports.parse(constants.NODE_VERSION)?.major ?? 0) < 20) {
|
|
1493
|
+
nodeVerWarn += vendor.yoctocolorsCjsExports.bold(
|
|
1494
|
+
` ${vendor.yoctocolorsCjsExports.red('Warning:')} NodeJS version 19 and lower will be ${vendor.yoctocolorsCjsExports.red('unsupported')} after April 30th, 2025.`
|
|
1495
|
+
)
|
|
1496
|
+
nodeVerWarn += '\n'
|
|
1497
|
+
nodeVerWarn +=
|
|
1498
|
+
' Soon after the Socket CLI will require NodeJS version 20 or higher.'
|
|
1499
|
+
nodeVerWarn += '\n'
|
|
1500
|
+
}
|
|
1501
|
+
const body = `
|
|
1502
|
+
_____ _ _ /---------------
|
|
1503
|
+
| __|___ ___| |_ ___| |_ | Socket.dev CLI ver ${cliVersion}${v1test}
|
|
1504
|
+
|__ | ${readOnlyConfig} | _| '_| -_| _| | Node: ${nodeVersion}, API token set: ${shownToken}${defaultOrg ? `, default org: ${redacting ? REDACTED : defaultOrg}` : ''}
|
|
1505
|
+
|_____|___|___|_,_|___|_|.dev | Command: \`${command}\`, cwd: ${relCwd}`.trimStart()
|
|
1506
|
+
return ` ${body}\n${nodeVerWarn}${feedback}`
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
async function suggestOrgSlug() {
|
|
1510
|
+
const sockSdkResult = await setupSdk()
|
|
1511
|
+
if (!sockSdkResult.ok) {
|
|
1512
|
+
return
|
|
1513
|
+
}
|
|
1514
|
+
const sockSdk = sockSdkResult.data
|
|
1515
|
+
const result = await handleApiCall(
|
|
1516
|
+
sockSdk.getOrganizations(),
|
|
1517
|
+
'list of organizations'
|
|
1518
|
+
)
|
|
1519
|
+
|
|
1520
|
+
// Ignore a failed request here. It was not the primary goal of
|
|
1521
|
+
// running this command and reporting it only leads to end-user confusion.
|
|
1522
|
+
if (result.ok) {
|
|
1523
|
+
const proceed = await prompts.select({
|
|
1524
|
+
message:
|
|
1525
|
+
'Missing org name; do you want to use any of these orgs for this scan?',
|
|
1526
|
+
choices: [
|
|
1527
|
+
...Object.values(result.data.organizations).map(org => {
|
|
1528
|
+
const name = org.name ?? org.slug
|
|
1529
|
+
return {
|
|
1530
|
+
name: `Yes [${name}]`,
|
|
1531
|
+
value: name,
|
|
1532
|
+
description: `Use "${name}" as the organization`
|
|
1533
|
+
}
|
|
1534
|
+
}),
|
|
1535
|
+
{
|
|
1536
|
+
name: 'No',
|
|
1537
|
+
value: '',
|
|
1538
|
+
description:
|
|
1539
|
+
'Do not use any of these organizations (will end in a no-op)'
|
|
1540
|
+
}
|
|
1541
|
+
]
|
|
1542
|
+
})
|
|
1543
|
+
if (proceed) {
|
|
1544
|
+
return proceed
|
|
1545
|
+
}
|
|
1546
|
+
} else {
|
|
1547
|
+
logger.logger.fail(
|
|
1548
|
+
'Failed to lookup organization list from API, unable to suggest'
|
|
1549
|
+
)
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
async function determineOrgSlug(orgFlag, firstArg, interactive, dryRun) {
|
|
1554
|
+
const defaultOrgSlug = getConfigValueOrUndef('defaultOrg')
|
|
1555
|
+
let orgSlug = String(orgFlag || defaultOrgSlug || '')
|
|
1556
|
+
if (!orgSlug) {
|
|
1557
|
+
if (isTestingV1()) {
|
|
1558
|
+
// ask from server
|
|
1559
|
+
logger.logger.error(
|
|
1560
|
+
'Missing the org slug and no --org flag set. Trying to auto-discover the org now...'
|
|
1561
|
+
)
|
|
1562
|
+
logger.logger.error(
|
|
1563
|
+
'Note: you can set the default org slug to prevent this issue. You can also override all that with the --org flag.'
|
|
1564
|
+
)
|
|
1565
|
+
if (dryRun) {
|
|
1566
|
+
logger.logger.fail('Skipping auto-discovery of org in dry-run mode')
|
|
1567
|
+
} else if (!interactive) {
|
|
1568
|
+
logger.logger.fail(
|
|
1569
|
+
'Skipping auto-discovery of org when interactive = false'
|
|
1570
|
+
)
|
|
1571
|
+
} else {
|
|
1572
|
+
orgSlug = (await suggestOrgSlug()) || ''
|
|
1573
|
+
}
|
|
1574
|
+
} else {
|
|
1575
|
+
orgSlug = firstArg || ''
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
return [orgSlug, defaultOrgSlug]
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
const { NODE_MODULES: NODE_MODULES$1, NPM: NPM$5, shadowBinPath } = constants
|
|
1582
|
+
function findBinPathDetailsSync(binName) {
|
|
1583
|
+
const binPaths =
|
|
1584
|
+
vendor.libExports$1.sync(binName, {
|
|
1585
|
+
all: true,
|
|
1586
|
+
nothrow: true
|
|
1587
|
+
}) ?? []
|
|
1588
|
+
let shadowIndex = -1
|
|
1589
|
+
let theBinPath
|
|
1590
|
+
for (let i = 0, { length } = binPaths; i < length; i += 1) {
|
|
1591
|
+
const binPath = binPaths[i]
|
|
1592
|
+
// Skip our bin directory if it's in the front.
|
|
1593
|
+
if (path.dirname(binPath) === shadowBinPath) {
|
|
1594
|
+
shadowIndex = i
|
|
1595
|
+
} else {
|
|
1596
|
+
theBinPath = npm.resolveBinPath(binPath)
|
|
1597
|
+
break
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
return {
|
|
1601
|
+
name: binName,
|
|
1602
|
+
path: theBinPath,
|
|
1603
|
+
shadowed: shadowIndex !== -1
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
function findNpmPathSync(npmBinPath) {
|
|
1607
|
+
// Lazily access constants.WIN32.
|
|
1608
|
+
const { WIN32 } = constants
|
|
1609
|
+
let thePath = npmBinPath
|
|
1610
|
+
while (true) {
|
|
1611
|
+
const libNmNpmPath = path.join(thePath, 'lib', NODE_MODULES$1, NPM$5)
|
|
1612
|
+
// mise puts its npm bin in a path like:
|
|
1613
|
+
// /Users/SomeUsername/.local/share/mise/installs/node/vX.X.X/bin/npm.
|
|
1614
|
+
// HOWEVER, the location of the npm install is:
|
|
1615
|
+
// /Users/SomeUsername/.local/share/mise/installs/node/vX.X.X/lib/node_modules/npm.
|
|
1616
|
+
if (
|
|
1617
|
+
// Use existsSync here because statsSync, even with { throwIfNoEntry: false },
|
|
1618
|
+
// will throw an ENOTDIR error for paths like ./a-file-that-exists/a-directory-that-does-not.
|
|
1619
|
+
// See https://github.com/nodejs/node/issues/56993.
|
|
1620
|
+
fs.existsSync(libNmNpmPath) &&
|
|
1621
|
+
fs
|
|
1622
|
+
.statSync(libNmNpmPath, {
|
|
1623
|
+
throwIfNoEntry: false
|
|
1624
|
+
})
|
|
1625
|
+
?.isDirectory()
|
|
1626
|
+
) {
|
|
1627
|
+
thePath = path.join(libNmNpmPath, NPM$5)
|
|
1628
|
+
}
|
|
1629
|
+
const nmPath = path.join(thePath, NODE_MODULES$1)
|
|
1630
|
+
if (
|
|
1631
|
+
// npm bin paths may look like:
|
|
1632
|
+
// /usr/local/share/npm/bin/npm
|
|
1633
|
+
// /Users/SomeUsername/.nvm/versions/node/vX.X.X/bin/npm
|
|
1634
|
+
// C:\Users\SomeUsername\AppData\Roaming\npm\bin\npm.cmd
|
|
1635
|
+
// OR
|
|
1636
|
+
// C:\Program Files\nodejs\npm.cmd
|
|
1637
|
+
//
|
|
1638
|
+
// In practically all cases the npm path contains a node_modules folder:
|
|
1639
|
+
// /usr/local/share/npm/bin/npm/node_modules
|
|
1640
|
+
// C:\Program Files\nodejs\node_modules
|
|
1641
|
+
fs.existsSync(nmPath) &&
|
|
1642
|
+
fs
|
|
1643
|
+
.statSync(nmPath, {
|
|
1644
|
+
throwIfNoEntry: false
|
|
1645
|
+
})
|
|
1646
|
+
?.isDirectory() &&
|
|
1647
|
+
// Optimistically look for the default location.
|
|
1648
|
+
(path.basename(thePath) === NPM$5 ||
|
|
1649
|
+
// Chocolatey installs npm bins in the same directory as node bins.
|
|
1650
|
+
(WIN32 && fs.existsSync(path.join(thePath, `${NPM$5}.cmd`))))
|
|
1651
|
+
) {
|
|
1652
|
+
return thePath
|
|
1653
|
+
}
|
|
1654
|
+
const parent = path.dirname(thePath)
|
|
1655
|
+
if (parent === thePath) {
|
|
1656
|
+
return undefined
|
|
1657
|
+
}
|
|
1658
|
+
thePath = parent
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
async function getPackageFilesForScan(cwd, inputPaths, supportedFiles, config) {
|
|
1662
|
+
debug.debugLog(
|
|
1663
|
+
`getPackageFilesForScan: resolving ${inputPaths.length} paths:\n`,
|
|
1664
|
+
inputPaths
|
|
1665
|
+
)
|
|
1666
|
+
|
|
1667
|
+
// Lazily access constants.spinner.
|
|
1668
|
+
const { spinner } = constants
|
|
1669
|
+
const patterns = pathsToGlobPatterns(inputPaths)
|
|
1670
|
+
spinner.start('Searching for local files to include in scan...')
|
|
1671
|
+
const entries = await globWithGitIgnore(patterns, {
|
|
1672
|
+
cwd,
|
|
1673
|
+
socketConfig: config
|
|
1674
|
+
})
|
|
1675
|
+
if (debug.isDebug()) {
|
|
1676
|
+
spinner.stop()
|
|
1677
|
+
debug.debugLog(
|
|
1678
|
+
`Resolved ${inputPaths.length} paths to ${entries.length} local paths:\n`,
|
|
1679
|
+
entries
|
|
1680
|
+
)
|
|
1681
|
+
spinner.start('Searching for files now...')
|
|
1682
|
+
} else {
|
|
1683
|
+
spinner.start(
|
|
1684
|
+
`Resolved ${inputPaths.length} paths to ${entries.length} local paths, searching for files now...`
|
|
1685
|
+
)
|
|
1686
|
+
}
|
|
1687
|
+
const packageFiles = await filterGlobResultToSupportedFiles(
|
|
1688
|
+
entries,
|
|
1689
|
+
supportedFiles
|
|
1690
|
+
)
|
|
1691
|
+
spinner.successAndStop(
|
|
1692
|
+
`Found ${packageFiles.length} local ${words.pluralize('file', packageFiles.length)}`
|
|
1693
|
+
)
|
|
1694
|
+
debug.debugLog('Absolute paths:\n', packageFiles)
|
|
1695
|
+
return packageFiles
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
const { NODE_MODULES, NPM: NPM$4, NPX, SOCKET_CLI_ISSUES_URL } = constants
|
|
1699
|
+
function exitWithBinPathError(binName) {
|
|
1700
|
+
logger.logger.fail(
|
|
1701
|
+
`Socket unable to locate ${binName}; ensure it is available in the PATH environment variable`
|
|
1702
|
+
)
|
|
1703
|
+
// The exit code 127 indicates that the command or binary being executed
|
|
1704
|
+
// could not be found.
|
|
1705
|
+
// eslint-disable-next-line n/no-process-exit
|
|
1706
|
+
process.exit(127)
|
|
1707
|
+
}
|
|
1708
|
+
let _npmBinPathDetails
|
|
1709
|
+
function getNpmBinPathDetails() {
|
|
1710
|
+
if (_npmBinPathDetails === undefined) {
|
|
1711
|
+
_npmBinPathDetails = findBinPathDetailsSync(NPM$4)
|
|
1712
|
+
}
|
|
1713
|
+
return _npmBinPathDetails
|
|
1714
|
+
}
|
|
1715
|
+
let _npxBinPathDetails
|
|
1716
|
+
function getNpxBinPathDetails() {
|
|
1717
|
+
if (_npxBinPathDetails === undefined) {
|
|
1718
|
+
_npxBinPathDetails = findBinPathDetailsSync(NPX)
|
|
1719
|
+
}
|
|
1720
|
+
return _npxBinPathDetails
|
|
1721
|
+
}
|
|
1722
|
+
function isNpmBinPathShadowed() {
|
|
1723
|
+
return getNpmBinPathDetails().shadowed
|
|
1724
|
+
}
|
|
1725
|
+
function isNpxBinPathShadowed() {
|
|
1726
|
+
return getNpxBinPathDetails().shadowed
|
|
1727
|
+
}
|
|
1728
|
+
let _npmBinPath
|
|
1729
|
+
function getNpmBinPath() {
|
|
1730
|
+
if (_npmBinPath === undefined) {
|
|
1731
|
+
_npmBinPath = getNpmBinPathDetails().path
|
|
1732
|
+
if (!_npmBinPath) {
|
|
1733
|
+
exitWithBinPathError(NPM$4)
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
return _npmBinPath
|
|
1737
|
+
}
|
|
1738
|
+
let _npmPath
|
|
1739
|
+
function getNpmPath() {
|
|
1740
|
+
if (_npmPath === undefined) {
|
|
1741
|
+
const npmBinPath = getNpmBinPath()
|
|
1742
|
+
_npmPath = npmBinPath ? findNpmPathSync(npmBinPath) : undefined
|
|
1743
|
+
if (!_npmPath) {
|
|
1744
|
+
let message = 'Unable to find npm CLI install directory.'
|
|
1745
|
+
if (npmBinPath) {
|
|
1746
|
+
message += `\nSearched parent directories of ${path.dirname(npmBinPath)}.`
|
|
1747
|
+
}
|
|
1748
|
+
message += `\n\nThis is may be a bug with socket-npm related to changes to the npm CLI.\nPlease report to ${SOCKET_CLI_ISSUES_URL}.`
|
|
1749
|
+
logger.logger.fail(message)
|
|
1750
|
+
// The exit code 127 indicates that the command or binary being executed
|
|
1751
|
+
// could not be found.
|
|
1752
|
+
// eslint-disable-next-line n/no-process-exit
|
|
1753
|
+
process.exit(127)
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
return _npmPath
|
|
1757
|
+
}
|
|
1758
|
+
let _npmRequire
|
|
1759
|
+
function getNpmRequire() {
|
|
1760
|
+
if (_npmRequire === undefined) {
|
|
1761
|
+
const npmPath = getNpmPath()
|
|
1762
|
+
const npmNmPath = path.join(npmPath, NODE_MODULES, NPM$4)
|
|
1763
|
+
_npmRequire = Module.createRequire(
|
|
1764
|
+
path.join(
|
|
1765
|
+
fs.existsSync(npmNmPath) ? npmNmPath : npmPath,
|
|
1766
|
+
'<dummy-basename>'
|
|
1767
|
+
)
|
|
1768
|
+
)
|
|
1769
|
+
}
|
|
1770
|
+
return _npmRequire
|
|
1771
|
+
}
|
|
1772
|
+
let _npxBinPath
|
|
1773
|
+
function getNpxBinPath() {
|
|
1774
|
+
if (_npxBinPath === undefined) {
|
|
1775
|
+
_npxBinPath = getNpxBinPathDetails().path
|
|
1776
|
+
if (!_npxBinPath) {
|
|
1777
|
+
exitWithBinPathError(NPX)
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
return _npxBinPath
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
const helpFlags = new Set(['--help', '-h'])
|
|
1784
|
+
function cmdFlagsToString(args) {
|
|
1785
|
+
const result = []
|
|
1786
|
+
for (let i = 0, { length } = args; i < length; i += 1) {
|
|
1787
|
+
if (args[i].startsWith('--')) {
|
|
1788
|
+
// Check if the next item exists and is NOT another flag.
|
|
1789
|
+
if (i + 1 < length && !args[i + 1].startsWith('--')) {
|
|
1790
|
+
result.push(`${args[i]}=${args[i + 1]}`)
|
|
1791
|
+
i += 1
|
|
1792
|
+
} else {
|
|
1793
|
+
result.push(args[i])
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
return result.join(' ')
|
|
1798
|
+
}
|
|
1799
|
+
function cmdPrefixMessage(cmdName, text) {
|
|
1800
|
+
const cmdPrefix = cmdName ? `${cmdName}: ` : ''
|
|
1801
|
+
return `${cmdPrefix}${text}`
|
|
1802
|
+
}
|
|
1803
|
+
function isHelpFlag(cmdArg) {
|
|
1804
|
+
return helpFlags.has(cmdArg)
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
function getPkgFullNameFromPurlObj(purlObj) {
|
|
1808
|
+
const { name, namespace } = purlObj
|
|
1809
|
+
return namespace
|
|
1810
|
+
? `${namespace}${purlObj.type === 'maven' ? ':' : '/'}${name}`
|
|
1811
|
+
: name
|
|
1812
|
+
}
|
|
1813
|
+
function getSocketDevAlertUrl(alertType) {
|
|
1814
|
+
return `https://socket.dev/alerts/${alertType}`
|
|
1815
|
+
}
|
|
1816
|
+
function getSocketDevPackageOverviewUrlFromPurl(purlObj) {
|
|
1817
|
+
const fullName = getPkgFullNameFromPurlObj(purlObj)
|
|
1818
|
+
return getSocketDevPackageOverviewUrl(purlObj.type, fullName, purlObj.version)
|
|
1819
|
+
}
|
|
1820
|
+
function getSocketDevPackageOverviewUrl(ecosystem, fullName, version) {
|
|
1821
|
+
if (ecosystem === 'go') {
|
|
1822
|
+
return `https://socket.dev/go/package/${fullName}${version ? `?section=overview&version=${version}` : ''}`
|
|
1823
|
+
} else {
|
|
1824
|
+
return `https://socket.dev/${ecosystem}/package/${fullName}${version ? `/overview/${version}` : ''}`
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
/**
|
|
1829
|
+
* Convert a Map<string, Map|string> to a nested object of similar shape.
|
|
1830
|
+
* The goal is to serialize it with JSON.stringify, which Map can't do.
|
|
1831
|
+
*/
|
|
1832
|
+
function mapToObject(map) {
|
|
1833
|
+
return Object.fromEntries(
|
|
1834
|
+
Array.from(map.entries()).map(([k, v]) => [
|
|
1835
|
+
k,
|
|
1836
|
+
v instanceof Map ? mapToObject(v) : v
|
|
1837
|
+
])
|
|
1838
|
+
)
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
function* walkNestedMap(map, keys = []) {
|
|
1842
|
+
for (const [key, value] of map.entries()) {
|
|
1843
|
+
if (value instanceof Map) {
|
|
1844
|
+
yield* walkNestedMap(value, keys.concat(key))
|
|
1845
|
+
} else {
|
|
1846
|
+
yield {
|
|
1847
|
+
keys: keys.concat(key),
|
|
1848
|
+
value: value
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
const {
|
|
1855
|
+
ALERT_TYPE_CRITICAL_CVE,
|
|
1856
|
+
ALERT_TYPE_CVE,
|
|
1857
|
+
ALERT_TYPE_MEDIUM_CVE,
|
|
1858
|
+
ALERT_TYPE_MILD_CVE
|
|
1859
|
+
} = constants
|
|
1860
|
+
function isArtifactAlertCve(alert) {
|
|
1861
|
+
const { type } = alert
|
|
1862
|
+
return (
|
|
1863
|
+
type === ALERT_TYPE_CVE ||
|
|
1864
|
+
type === ALERT_TYPE_MEDIUM_CVE ||
|
|
1865
|
+
type === ALERT_TYPE_MILD_CVE ||
|
|
1866
|
+
type === ALERT_TYPE_CRITICAL_CVE
|
|
1867
|
+
)
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
function createEnum(obj) {
|
|
1871
|
+
return Object.freeze({
|
|
1872
|
+
__proto__: null,
|
|
1873
|
+
...obj
|
|
1874
|
+
})
|
|
1875
|
+
}
|
|
1876
|
+
function pick(input, keys) {
|
|
1877
|
+
const result = {}
|
|
1878
|
+
for (const key of keys) {
|
|
1879
|
+
result[key] = input[key]
|
|
1880
|
+
}
|
|
1881
|
+
return result
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
const ALERT_FIX_TYPE = createEnum({
|
|
1885
|
+
cve: 'cve',
|
|
1886
|
+
remove: 'remove',
|
|
1887
|
+
upgrade: 'upgrade'
|
|
1888
|
+
})
|
|
1889
|
+
|
|
1890
|
+
function stringJoinWithSeparateFinalSeparator(list, separator = ' and ') {
|
|
1891
|
+
const values = list.filter(Boolean)
|
|
1892
|
+
const { length } = values
|
|
1893
|
+
if (!length) {
|
|
1894
|
+
return ''
|
|
1895
|
+
}
|
|
1896
|
+
if (length === 1) {
|
|
1897
|
+
return values[0]
|
|
1898
|
+
}
|
|
1899
|
+
const finalValue = values.pop()
|
|
1900
|
+
return `${values.join(', ')}${separator}${finalValue}`
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
const ALERT_SEVERITY = createEnum({
|
|
1904
|
+
critical: 'critical',
|
|
1905
|
+
high: 'high',
|
|
1906
|
+
middle: 'middle',
|
|
1907
|
+
low: 'low'
|
|
1908
|
+
})
|
|
1909
|
+
// Ordered from most severe to least.
|
|
1910
|
+
const ALERT_SEVERITIES_SORTED = Object.freeze([
|
|
1911
|
+
'critical',
|
|
1912
|
+
'high',
|
|
1913
|
+
'middle',
|
|
1914
|
+
'low'
|
|
1915
|
+
])
|
|
1916
|
+
function getDesiredSeverities(lowestToInclude) {
|
|
1917
|
+
const result = []
|
|
1918
|
+
for (const severity of ALERT_SEVERITIES_SORTED) {
|
|
1919
|
+
result.push(severity)
|
|
1920
|
+
if (severity === lowestToInclude) {
|
|
1921
|
+
break
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
return result
|
|
1925
|
+
}
|
|
1926
|
+
function formatSeverityCount(severityCount) {
|
|
1927
|
+
const summary = []
|
|
1928
|
+
for (const severity of ALERT_SEVERITIES_SORTED) {
|
|
1929
|
+
if (severityCount[severity]) {
|
|
1930
|
+
summary.push(`${severityCount[severity]} ${severity}`)
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
return stringJoinWithSeparateFinalSeparator(summary)
|
|
1934
|
+
}
|
|
1935
|
+
function getSeverityCount(issues, lowestToInclude) {
|
|
1936
|
+
const severityCount = pick(
|
|
1937
|
+
{
|
|
1938
|
+
low: 0,
|
|
1939
|
+
middle: 0,
|
|
1940
|
+
high: 0,
|
|
1941
|
+
critical: 0
|
|
1942
|
+
},
|
|
1943
|
+
getDesiredSeverities(lowestToInclude)
|
|
1944
|
+
)
|
|
1945
|
+
for (const issue of issues) {
|
|
1946
|
+
const { value } = issue
|
|
1947
|
+
if (!value) {
|
|
1948
|
+
continue
|
|
1949
|
+
}
|
|
1950
|
+
const { severity } = value
|
|
1951
|
+
if (severityCount[severity] !== undefined) {
|
|
1952
|
+
severityCount[severity] += 1
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
return severityCount
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
class ColorOrMarkdown {
|
|
1959
|
+
constructor(useMarkdown) {
|
|
1960
|
+
this.useMarkdown = !!useMarkdown
|
|
1961
|
+
}
|
|
1962
|
+
bold(text) {
|
|
1963
|
+
return this.useMarkdown
|
|
1964
|
+
? `**${text}**`
|
|
1965
|
+
: vendor.yoctocolorsCjsExports.bold(`${text}`)
|
|
1966
|
+
}
|
|
1967
|
+
header(text, level = 1) {
|
|
1968
|
+
return this.useMarkdown
|
|
1969
|
+
? `\n${''.padStart(level, '#')} ${text}\n`
|
|
1970
|
+
: vendor.yoctocolorsCjsExports.underline(
|
|
1971
|
+
`\n${level === 1 ? vendor.yoctocolorsCjsExports.bold(text) : text}\n`
|
|
1972
|
+
)
|
|
1973
|
+
}
|
|
1974
|
+
hyperlink(text, url, { fallback = true, fallbackToUrl } = {}) {
|
|
1975
|
+
if (url) {
|
|
1976
|
+
return this.useMarkdown
|
|
1977
|
+
? `[${text}](${url})`
|
|
1978
|
+
: vendor.terminalLinkExports(text, url, {
|
|
1979
|
+
fallback: fallbackToUrl ? (_text, url) => url : fallback
|
|
1980
|
+
})
|
|
1981
|
+
}
|
|
1982
|
+
return text
|
|
1983
|
+
}
|
|
1984
|
+
indent(...args) {
|
|
1985
|
+
return vendor.indentStringExports(...args)
|
|
1986
|
+
}
|
|
1987
|
+
italic(text) {
|
|
1988
|
+
return this.useMarkdown
|
|
1989
|
+
? `_${text}_`
|
|
1990
|
+
: vendor.yoctocolorsCjsExports.italic(`${text}`)
|
|
1991
|
+
}
|
|
1992
|
+
json(value) {
|
|
1993
|
+
return this.useMarkdown
|
|
1994
|
+
? '```json\n' + JSON.stringify(value) + '\n```'
|
|
1995
|
+
: JSON.stringify(value)
|
|
1996
|
+
}
|
|
1997
|
+
list(items) {
|
|
1998
|
+
const indentedContent = items.map(item => this.indent(item).trimStart())
|
|
1999
|
+
return this.useMarkdown
|
|
2000
|
+
? `* ${indentedContent.join('\n* ')}\n`
|
|
2001
|
+
: `${indentedContent.join('\n')}\n`
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
const require$1 = Module.createRequire(
|
|
2006
|
+
require('u' + 'rl').pathToFileURL(__filename).href
|
|
2007
|
+
)
|
|
2008
|
+
let _translations
|
|
2009
|
+
function getTranslations() {
|
|
2010
|
+
if (_translations === undefined) {
|
|
2011
|
+
_translations = require$1(
|
|
2012
|
+
// Lazily access constants.rootPath.
|
|
2013
|
+
path.join(constants.rootPath, 'translations.json')
|
|
2014
|
+
)
|
|
2015
|
+
}
|
|
2016
|
+
return _translations
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
function idToPurl(id) {
|
|
2020
|
+
return `pkg:npm/${id}`
|
|
2021
|
+
}
|
|
2022
|
+
function resolvePackageVersion(purlObj) {
|
|
2023
|
+
const { version } = purlObj
|
|
2024
|
+
return version
|
|
2025
|
+
? (vendor.semverExports.coerce(stripPeerSuffix(version))?.version ?? '')
|
|
2026
|
+
: ''
|
|
2027
|
+
}
|
|
2028
|
+
function stripLeadingSlash(path) {
|
|
2029
|
+
return path.startsWith('/') ? path.slice(1) : path
|
|
2030
|
+
}
|
|
2031
|
+
function stripPeerSuffix(depPath) {
|
|
2032
|
+
const idx = depPath.indexOf('(')
|
|
2033
|
+
return idx === -1 ? depPath : depPath.slice(0, idx)
|
|
2034
|
+
}
|
|
2035
|
+
|
|
2036
|
+
const ALERT_SEVERITY_COLOR = createEnum({
|
|
2037
|
+
critical: 'magenta',
|
|
2038
|
+
high: 'red',
|
|
2039
|
+
middle: 'yellow',
|
|
2040
|
+
low: 'white'
|
|
2041
|
+
})
|
|
2042
|
+
const ALERT_SEVERITY_ORDER = createEnum({
|
|
2043
|
+
critical: 0,
|
|
2044
|
+
high: 1,
|
|
2045
|
+
middle: 2,
|
|
2046
|
+
low: 3,
|
|
2047
|
+
none: 4
|
|
2048
|
+
})
|
|
2049
|
+
const { CVE_ALERT_PROPS_FIRST_PATCHED_VERSION_IDENTIFIER, NPM: NPM$3 } =
|
|
2050
|
+
constants
|
|
2051
|
+
const MIN_ABOVE_THE_FOLD_COUNT = 3
|
|
2052
|
+
const MIN_ABOVE_THE_FOLD_ALERT_COUNT = 1
|
|
2053
|
+
const format = new ColorOrMarkdown(false)
|
|
2054
|
+
function alertsHaveBlocked(alerts) {
|
|
2055
|
+
return alerts.find(a => a.blocked) !== undefined
|
|
2056
|
+
}
|
|
2057
|
+
function alertsHaveSeverity(alerts, severity) {
|
|
2058
|
+
return alerts.find(a => a.raw.severity === severity) !== undefined
|
|
2059
|
+
}
|
|
2060
|
+
function alertSeverityComparator(a, b) {
|
|
2061
|
+
return getAlertSeverityOrder(a) - getAlertSeverityOrder(b)
|
|
2062
|
+
}
|
|
2063
|
+
function getAlertSeverityOrder(alert) {
|
|
2064
|
+
const { severity } = alert.raw
|
|
2065
|
+
return severity === ALERT_SEVERITY.critical
|
|
2066
|
+
? 0
|
|
2067
|
+
: severity === ALERT_SEVERITY.high
|
|
2068
|
+
? 1
|
|
2069
|
+
: severity === ALERT_SEVERITY.middle
|
|
2070
|
+
? 2
|
|
2071
|
+
: severity === ALERT_SEVERITY.low
|
|
2072
|
+
? 3
|
|
2073
|
+
: 4
|
|
2074
|
+
}
|
|
2075
|
+
function getAlertsSeverityOrder(alerts) {
|
|
2076
|
+
return alertsHaveBlocked(alerts) ||
|
|
2077
|
+
alertsHaveSeverity(alerts, ALERT_SEVERITY.critical)
|
|
2078
|
+
? 0
|
|
2079
|
+
: alertsHaveSeverity(alerts, ALERT_SEVERITY.high)
|
|
2080
|
+
? 1
|
|
2081
|
+
: alertsHaveSeverity(alerts, ALERT_SEVERITY.middle)
|
|
2082
|
+
? 2
|
|
2083
|
+
: alertsHaveSeverity(alerts, ALERT_SEVERITY.low)
|
|
2084
|
+
? 3
|
|
2085
|
+
: 4
|
|
2086
|
+
}
|
|
2087
|
+
function getHiddenRiskCounts(hiddenAlerts) {
|
|
2088
|
+
const riskCounts = {
|
|
2089
|
+
critical: 0,
|
|
2090
|
+
high: 0,
|
|
2091
|
+
middle: 0,
|
|
2092
|
+
low: 0
|
|
2093
|
+
}
|
|
2094
|
+
for (const alert of hiddenAlerts) {
|
|
2095
|
+
switch (getAlertSeverityOrder(alert)) {
|
|
2096
|
+
case ALERT_SEVERITY_ORDER.critical:
|
|
2097
|
+
riskCounts.critical += 1
|
|
2098
|
+
break
|
|
2099
|
+
case ALERT_SEVERITY_ORDER.high:
|
|
2100
|
+
riskCounts.high += 1
|
|
2101
|
+
break
|
|
2102
|
+
case ALERT_SEVERITY_ORDER.middle:
|
|
2103
|
+
riskCounts.middle += 1
|
|
2104
|
+
break
|
|
2105
|
+
case ALERT_SEVERITY_ORDER.low:
|
|
2106
|
+
riskCounts.low += 1
|
|
2107
|
+
break
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
return riskCounts
|
|
2111
|
+
}
|
|
2112
|
+
function getHiddenRisksDescription(riskCounts) {
|
|
2113
|
+
const descriptions = []
|
|
2114
|
+
if (riskCounts.critical) {
|
|
2115
|
+
descriptions.push(`${riskCounts.critical} ${getSeverityLabel('critical')}`)
|
|
2116
|
+
}
|
|
2117
|
+
if (riskCounts.high) {
|
|
2118
|
+
descriptions.push(`${riskCounts.high} ${getSeverityLabel('high')}`)
|
|
2119
|
+
}
|
|
2120
|
+
if (riskCounts.middle) {
|
|
2121
|
+
descriptions.push(`${riskCounts.middle} ${getSeverityLabel('middle')}`)
|
|
2122
|
+
}
|
|
2123
|
+
if (riskCounts.low) {
|
|
2124
|
+
descriptions.push(`${riskCounts.low} ${getSeverityLabel('low')}`)
|
|
2125
|
+
}
|
|
2126
|
+
return `(${descriptions.join('; ')})`
|
|
2127
|
+
}
|
|
2128
|
+
function getSeverityLabel(severity) {
|
|
2129
|
+
return severity === 'middle' ? 'moderate' : severity
|
|
2130
|
+
}
|
|
2131
|
+
async function addArtifactToAlertsMap(artifact, alertsByPkgId, options) {
|
|
2132
|
+
// Make TypeScript happy.
|
|
2133
|
+
if (!artifact.name || !artifact.version || !artifact.alerts?.length) {
|
|
2134
|
+
return alertsByPkgId
|
|
2135
|
+
}
|
|
2136
|
+
const {
|
|
2137
|
+
consolidate = false,
|
|
2138
|
+
include: _include,
|
|
2139
|
+
overrides
|
|
2140
|
+
} = {
|
|
2141
|
+
__proto__: null,
|
|
2142
|
+
...options
|
|
2143
|
+
}
|
|
2144
|
+
const include = {
|
|
2145
|
+
__proto__: null,
|
|
2146
|
+
blocked: true,
|
|
2147
|
+
critical: true,
|
|
2148
|
+
cve: true,
|
|
2149
|
+
unfixable: true,
|
|
2150
|
+
upgradable: false,
|
|
2151
|
+
..._include
|
|
2152
|
+
}
|
|
2153
|
+
const name = packages.resolvePackageName(artifact)
|
|
2154
|
+
const { version } = artifact
|
|
2155
|
+
const pkgId = `${name}@${version}`
|
|
2156
|
+
const major = vendor.semverExports.major(version)
|
|
2157
|
+
const socketYml = findSocketYmlSync()
|
|
2158
|
+
const enabledState = {
|
|
2159
|
+
__proto__: null,
|
|
2160
|
+
...socketYml?.parsed.issueRules
|
|
2161
|
+
}
|
|
2162
|
+
let sockPkgAlerts = []
|
|
2163
|
+
for (const alert of artifact.alerts) {
|
|
2164
|
+
const action = alert.action ?? ''
|
|
2165
|
+
const enabledFlag = enabledState[alert.type]
|
|
2166
|
+
if (
|
|
2167
|
+
(action === 'ignore' && enabledFlag !== true) ||
|
|
2168
|
+
enabledFlag === false
|
|
2169
|
+
) {
|
|
2170
|
+
continue
|
|
2171
|
+
}
|
|
2172
|
+
const blocked = action === 'error'
|
|
2173
|
+
const critical = alert.severity === ALERT_SEVERITY.critical
|
|
2174
|
+
const cve = isArtifactAlertCve(alert)
|
|
2175
|
+
const fixType = alert.fix?.type ?? ''
|
|
2176
|
+
const fixableCve = fixType === ALERT_FIX_TYPE.cve
|
|
2177
|
+
const fixableUpgrade = fixType === ALERT_FIX_TYPE.upgrade
|
|
2178
|
+
const fixable = fixableCve || fixableUpgrade
|
|
2179
|
+
const upgradable = fixableUpgrade && !objects.hasOwn(overrides, name)
|
|
2180
|
+
if (
|
|
2181
|
+
(include.blocked && blocked) ||
|
|
2182
|
+
(include.critical && critical) ||
|
|
2183
|
+
(include.cve && cve) ||
|
|
2184
|
+
(include.unfixable && !fixable) ||
|
|
2185
|
+
(include.upgradable && upgradable)
|
|
2186
|
+
) {
|
|
2187
|
+
sockPkgAlerts.push({
|
|
2188
|
+
name,
|
|
2189
|
+
version,
|
|
2190
|
+
key: alert.key,
|
|
2191
|
+
type: alert.type,
|
|
2192
|
+
blocked,
|
|
2193
|
+
critical,
|
|
2194
|
+
fixable,
|
|
2195
|
+
raw: alert,
|
|
2196
|
+
upgradable
|
|
2197
|
+
})
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
if (!sockPkgAlerts.length) {
|
|
2201
|
+
return alertsByPkgId
|
|
2202
|
+
}
|
|
2203
|
+
if (consolidate) {
|
|
2204
|
+
const highestForCve = new Map()
|
|
2205
|
+
const highestForUpgrade = new Map()
|
|
2206
|
+
const unfixableAlerts = []
|
|
2207
|
+
for (const sockPkgAlert of sockPkgAlerts) {
|
|
2208
|
+
const alert = sockPkgAlert.raw
|
|
2209
|
+
const fixType = alert.fix?.type ?? ''
|
|
2210
|
+
if (fixType === ALERT_FIX_TYPE.cve) {
|
|
2211
|
+
const patchedVersion =
|
|
2212
|
+
alert.props[CVE_ALERT_PROPS_FIRST_PATCHED_VERSION_IDENTIFIER]
|
|
2213
|
+
const patchedMajor = vendor.semverExports.major(patchedVersion)
|
|
2214
|
+
const oldHighest = highestForCve.get(patchedMajor)
|
|
2215
|
+
const highest = oldHighest?.version ?? '0.0.0'
|
|
2216
|
+
if (vendor.semverExports.gt(patchedVersion, highest)) {
|
|
2217
|
+
highestForCve.set(patchedMajor, {
|
|
2218
|
+
alert: sockPkgAlert,
|
|
2219
|
+
version: patchedVersion
|
|
2220
|
+
})
|
|
2221
|
+
}
|
|
2222
|
+
} else if (fixType === ALERT_FIX_TYPE.upgrade) {
|
|
2223
|
+
const oldHighest = highestForUpgrade.get(major)
|
|
2224
|
+
const highest = oldHighest?.version ?? '0.0.0'
|
|
2225
|
+
if (vendor.semverExports.gt(version, highest)) {
|
|
2226
|
+
highestForUpgrade.set(major, {
|
|
2227
|
+
alert: sockPkgAlert,
|
|
2228
|
+
version
|
|
2229
|
+
})
|
|
2230
|
+
}
|
|
2231
|
+
} else {
|
|
2232
|
+
unfixableAlerts.push(sockPkgAlert)
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
sockPkgAlerts = [
|
|
2236
|
+
...unfixableAlerts,
|
|
2237
|
+
...[...highestForCve.values()].map(d => d.alert),
|
|
2238
|
+
...[...highestForUpgrade.values()].map(d => d.alert)
|
|
2239
|
+
]
|
|
2240
|
+
}
|
|
2241
|
+
if (sockPkgAlerts.length) {
|
|
2242
|
+
sockPkgAlerts.sort((a, b) => sorts.naturalCompare(a.type, b.type))
|
|
2243
|
+
alertsByPkgId.set(pkgId, sockPkgAlerts)
|
|
2244
|
+
}
|
|
2245
|
+
return alertsByPkgId
|
|
2246
|
+
}
|
|
2247
|
+
function getCveInfoByAlertsMap(alertsMap, options) {
|
|
2248
|
+
const { exclude: _exclude, limit = Infinity } = {
|
|
2249
|
+
__proto__: null,
|
|
2250
|
+
...options
|
|
2251
|
+
}
|
|
2252
|
+
const exclude = {
|
|
2253
|
+
__proto__: null,
|
|
2254
|
+
upgradable: true,
|
|
2255
|
+
..._exclude
|
|
2256
|
+
}
|
|
2257
|
+
let count = 0
|
|
2258
|
+
let infoByPkg = null
|
|
2259
|
+
alertsMapLoop: for (const [pkgId, sockPkgAlerts] of alertsMap) {
|
|
2260
|
+
const purlObj = vendor.packageurlJsExports.PackageURL.fromString(
|
|
2261
|
+
idToPurl(pkgId)
|
|
2262
|
+
)
|
|
2263
|
+
const name = packages.resolvePackageName(purlObj)
|
|
2264
|
+
for (const sockPkgAlert of sockPkgAlerts) {
|
|
2265
|
+
const alert = sockPkgAlert.raw
|
|
2266
|
+
if (
|
|
2267
|
+
alert.fix?.type !== ALERT_FIX_TYPE.cve ||
|
|
2268
|
+
(exclude.upgradable && registry.getManifestData(NPM$3, name))
|
|
2269
|
+
) {
|
|
2270
|
+
continue
|
|
2271
|
+
}
|
|
2272
|
+
if (!infoByPkg) {
|
|
2273
|
+
infoByPkg = new Map()
|
|
2274
|
+
}
|
|
2275
|
+
let infos = infoByPkg.get(name)
|
|
2276
|
+
if (!infos) {
|
|
2277
|
+
infos = []
|
|
2278
|
+
infoByPkg.set(name, infos)
|
|
2279
|
+
}
|
|
2280
|
+
const { firstPatchedVersionIdentifier, vulnerableVersionRange } =
|
|
2281
|
+
alert.props
|
|
2282
|
+
try {
|
|
2283
|
+
infos.push({
|
|
2284
|
+
firstPatchedVersionIdentifier,
|
|
2285
|
+
vulnerableVersionRange: new vendor.semverExports.Range(
|
|
2286
|
+
// Replace ', ' in a range like '>= 1.0.0, < 1.8.2' with ' ' so that
|
|
2287
|
+
// semver.Range will parse it without erroring.
|
|
2288
|
+
vulnerableVersionRange.replace(/, +/g, ' ')
|
|
2289
|
+
).format()
|
|
2290
|
+
})
|
|
2291
|
+
if (++count >= limit) {
|
|
2292
|
+
break alertsMapLoop
|
|
2293
|
+
}
|
|
2294
|
+
} catch (e) {
|
|
2295
|
+
debug.debugLog('getCveInfoByAlertsMap', {
|
|
2296
|
+
firstPatchedVersionIdentifier,
|
|
2297
|
+
vulnerableVersionRange
|
|
2298
|
+
})
|
|
2299
|
+
debug.debugLog(e)
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
return infoByPkg
|
|
2304
|
+
}
|
|
2305
|
+
function logAlertsMap(alertsMap, options) {
|
|
2306
|
+
const { hideAt = 'middle', output = process.stderr } = {
|
|
2307
|
+
__proto__: null,
|
|
2308
|
+
...options
|
|
2309
|
+
}
|
|
2310
|
+
const translations = getTranslations()
|
|
2311
|
+
const sortedEntries = [...alertsMap.entries()].sort(
|
|
2312
|
+
(a, b) => getAlertsSeverityOrder(a[1]) - getAlertsSeverityOrder(b[1])
|
|
2313
|
+
)
|
|
2314
|
+
const aboveTheFoldPkgIds = new Set()
|
|
2315
|
+
const viewableAlertsByPkgId = new Map()
|
|
2316
|
+
const hiddenAlertsByPkgId = new Map()
|
|
2317
|
+
for (let i = 0, { length } = sortedEntries; i < length; i += 1) {
|
|
2318
|
+
const { 0: pkgId, 1: alerts } = sortedEntries[i]
|
|
2319
|
+
const hiddenAlerts = []
|
|
2320
|
+
const viewableAlerts = alerts.filter(a => {
|
|
2321
|
+
const keep =
|
|
2322
|
+
a.blocked || getAlertSeverityOrder(a) < ALERT_SEVERITY_ORDER[hideAt]
|
|
2323
|
+
if (!keep) {
|
|
2324
|
+
hiddenAlerts.push(a)
|
|
2325
|
+
}
|
|
2326
|
+
return keep
|
|
2327
|
+
})
|
|
2328
|
+
if (hiddenAlerts.length) {
|
|
2329
|
+
hiddenAlertsByPkgId.set(pkgId, hiddenAlerts.sort(alertSeverityComparator))
|
|
2330
|
+
}
|
|
2331
|
+
if (!viewableAlerts.length) {
|
|
2332
|
+
continue
|
|
2333
|
+
}
|
|
2334
|
+
viewableAlerts.sort(alertSeverityComparator)
|
|
2335
|
+
viewableAlertsByPkgId.set(pkgId, viewableAlerts)
|
|
2336
|
+
if (
|
|
2337
|
+
viewableAlerts.find(
|
|
2338
|
+
a => a.blocked || getAlertSeverityOrder(a) < ALERT_SEVERITY_ORDER.middle
|
|
2339
|
+
)
|
|
2340
|
+
) {
|
|
2341
|
+
aboveTheFoldPkgIds.add(pkgId)
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
// If MIN_ABOVE_THE_FOLD_COUNT is NOT met add more from viewable pkg ids.
|
|
2346
|
+
for (const { 0: pkgId } of viewableAlertsByPkgId.entries()) {
|
|
2347
|
+
if (aboveTheFoldPkgIds.size >= MIN_ABOVE_THE_FOLD_COUNT) {
|
|
2348
|
+
break
|
|
2349
|
+
}
|
|
2350
|
+
aboveTheFoldPkgIds.add(pkgId)
|
|
2351
|
+
}
|
|
2352
|
+
// If MIN_ABOVE_THE_FOLD_COUNT is STILL NOT met add more from hidden pkg ids.
|
|
2353
|
+
for (const { 0: pkgId, 1: hiddenAlerts } of hiddenAlertsByPkgId.entries()) {
|
|
2354
|
+
if (aboveTheFoldPkgIds.size >= MIN_ABOVE_THE_FOLD_COUNT) {
|
|
2355
|
+
break
|
|
2356
|
+
}
|
|
2357
|
+
aboveTheFoldPkgIds.add(pkgId)
|
|
2358
|
+
const viewableAlerts = viewableAlertsByPkgId.get(pkgId) ?? []
|
|
2359
|
+
if (viewableAlerts.length < MIN_ABOVE_THE_FOLD_ALERT_COUNT) {
|
|
2360
|
+
const neededCount = MIN_ABOVE_THE_FOLD_ALERT_COUNT - viewableAlerts.length
|
|
2361
|
+
let removedHiddenAlerts
|
|
2362
|
+
if (hiddenAlerts.length - neededCount > 0) {
|
|
2363
|
+
removedHiddenAlerts = hiddenAlerts.splice(
|
|
2364
|
+
0,
|
|
2365
|
+
MIN_ABOVE_THE_FOLD_ALERT_COUNT
|
|
2366
|
+
)
|
|
2367
|
+
} else {
|
|
2368
|
+
removedHiddenAlerts = hiddenAlerts
|
|
2369
|
+
hiddenAlertsByPkgId.delete(pkgId)
|
|
2370
|
+
}
|
|
2371
|
+
viewableAlertsByPkgId.set(pkgId, [
|
|
2372
|
+
...viewableAlerts,
|
|
2373
|
+
...removedHiddenAlerts
|
|
2374
|
+
])
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
const mentionedPkgIdsWithHiddenAlerts = new Set()
|
|
2378
|
+
for (
|
|
2379
|
+
let i = 0,
|
|
2380
|
+
prevAboveTheFold = true,
|
|
2381
|
+
entries = [...viewableAlertsByPkgId.entries()],
|
|
2382
|
+
{ length } = entries;
|
|
2383
|
+
i < length;
|
|
2384
|
+
i += 1
|
|
2385
|
+
) {
|
|
2386
|
+
const { 0: pkgId, 1: alerts } = entries[i]
|
|
2387
|
+
const lines = new Set()
|
|
2388
|
+
for (const alert of alerts) {
|
|
2389
|
+
const { type } = alert
|
|
2390
|
+
const severity = alert.raw.severity ?? ''
|
|
2391
|
+
const attributes = [
|
|
2392
|
+
...(severity
|
|
2393
|
+
? [
|
|
2394
|
+
vendor.yoctocolorsCjsExports[ALERT_SEVERITY_COLOR[severity]](
|
|
2395
|
+
getSeverityLabel(severity)
|
|
2396
|
+
)
|
|
2397
|
+
]
|
|
2398
|
+
: []),
|
|
2399
|
+
...(alert.blocked
|
|
2400
|
+
? [
|
|
2401
|
+
vendor.yoctocolorsCjsExports.bold(
|
|
2402
|
+
vendor.yoctocolorsCjsExports.red('blocked')
|
|
2403
|
+
)
|
|
2404
|
+
]
|
|
2405
|
+
: []),
|
|
2406
|
+
...(alert.fixable ? ['fixable'] : [])
|
|
2407
|
+
]
|
|
2408
|
+
const maybeAttributes = attributes.length
|
|
2409
|
+
? ` ${vendor.yoctocolorsCjsExports.italic(`(${attributes.join('; ')})`)}`
|
|
2410
|
+
: ''
|
|
2411
|
+
// Based data from { pageProps: { alertTypes } } of:
|
|
2412
|
+
// https://socket.dev/_next/data/94666139314b6437ee4491a0864e72b264547585/en-US.json
|
|
2413
|
+
const info = translations.alerts[type]
|
|
2414
|
+
const title = info?.title ?? type
|
|
2415
|
+
const maybeDesc = info?.description ? ` - ${info.description}` : ''
|
|
2416
|
+
const content = `${title}${maybeAttributes}${maybeDesc}`
|
|
2417
|
+
// TODO: emoji seems to mis-align terminals sometimes
|
|
2418
|
+
lines.add(` ${content}`)
|
|
2419
|
+
}
|
|
2420
|
+
const purlObj = vendor.packageurlJsExports.PackageURL.fromString(
|
|
2421
|
+
idToPurl(pkgId)
|
|
2422
|
+
)
|
|
2423
|
+
const hyperlink = format.hyperlink(
|
|
2424
|
+
pkgId,
|
|
2425
|
+
getSocketDevPackageOverviewUrl(
|
|
2426
|
+
NPM$3,
|
|
2427
|
+
packages.resolvePackageName(purlObj),
|
|
2428
|
+
purlObj.version
|
|
2429
|
+
)
|
|
2430
|
+
)
|
|
2431
|
+
const isAboveTheFold = aboveTheFoldPkgIds.has(pkgId)
|
|
2432
|
+
if (isAboveTheFold) {
|
|
2433
|
+
aboveTheFoldPkgIds.add(pkgId)
|
|
2434
|
+
output.write(`${i ? '\n' : ''}${hyperlink}:\n`)
|
|
2435
|
+
} else {
|
|
2436
|
+
output.write(`${prevAboveTheFold ? '\n' : ''}${hyperlink}:\n`)
|
|
2437
|
+
}
|
|
2438
|
+
for (const line of lines) {
|
|
2439
|
+
output.write(`${line}\n`)
|
|
2440
|
+
}
|
|
2441
|
+
const hiddenAlerts = hiddenAlertsByPkgId.get(pkgId) ?? []
|
|
2442
|
+
const { length: hiddenAlertsCount } = hiddenAlerts
|
|
2443
|
+
if (hiddenAlertsCount) {
|
|
2444
|
+
mentionedPkgIdsWithHiddenAlerts.add(pkgId)
|
|
2445
|
+
if (hiddenAlertsCount === 1) {
|
|
2446
|
+
output.write(
|
|
2447
|
+
` ${vendor.yoctocolorsCjsExports.dim(`+1 Hidden ${getSeverityLabel(hiddenAlerts[0].raw.severity ?? 'low')} risk alert`)}\n`
|
|
2448
|
+
)
|
|
2449
|
+
} else {
|
|
2450
|
+
output.write(
|
|
2451
|
+
` ${vendor.yoctocolorsCjsExports.dim(`+${hiddenAlertsCount} Hidden alerts ${vendor.yoctocolorsCjsExports.italic(getHiddenRisksDescription(getHiddenRiskCounts(hiddenAlerts)))}`)}\n`
|
|
2452
|
+
)
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
prevAboveTheFold = isAboveTheFold
|
|
2456
|
+
}
|
|
2457
|
+
const additionalHiddenCount =
|
|
2458
|
+
hiddenAlertsByPkgId.size - mentionedPkgIdsWithHiddenAlerts.size
|
|
2459
|
+
if (additionalHiddenCount) {
|
|
2460
|
+
const totalRiskCounts = {
|
|
2461
|
+
critical: 0,
|
|
2462
|
+
high: 0,
|
|
2463
|
+
middle: 0,
|
|
2464
|
+
low: 0
|
|
2465
|
+
}
|
|
2466
|
+
for (const { 0: pkgId, 1: alerts } of hiddenAlertsByPkgId.entries()) {
|
|
2467
|
+
if (mentionedPkgIdsWithHiddenAlerts.has(pkgId)) {
|
|
2468
|
+
continue
|
|
2469
|
+
}
|
|
2470
|
+
const riskCounts = getHiddenRiskCounts(alerts)
|
|
2471
|
+
totalRiskCounts.critical += riskCounts.critical
|
|
2472
|
+
totalRiskCounts.high += riskCounts.high
|
|
2473
|
+
totalRiskCounts.middle += riskCounts.middle
|
|
2474
|
+
totalRiskCounts.low += riskCounts.low
|
|
2475
|
+
}
|
|
2476
|
+
output.write(
|
|
2477
|
+
`${aboveTheFoldPkgIds.size ? '\n' : ''}${vendor.yoctocolorsCjsExports.dim(`${aboveTheFoldPkgIds.size ? '+' : ''}${additionalHiddenCount} Packages with hidden alerts ${vendor.yoctocolorsCjsExports.italic(getHiddenRisksDescription(totalRiskCounts))}`)}\n`
|
|
2478
|
+
)
|
|
2479
|
+
}
|
|
2480
|
+
output.write('\n')
|
|
2481
|
+
}
|
|
2482
|
+
|
|
2483
|
+
const RangeStyles = ['caret', 'gt', 'lt', 'pin', 'preserve', 'tilde']
|
|
2484
|
+
function applyRange(refRange, version, style = 'preserve') {
|
|
2485
|
+
switch (style) {
|
|
2486
|
+
case 'caret':
|
|
2487
|
+
return `^${version}`
|
|
2488
|
+
case 'gt':
|
|
2489
|
+
return `>${version}`
|
|
2490
|
+
case 'gte':
|
|
2491
|
+
return `>=${version}`
|
|
2492
|
+
case 'lt':
|
|
2493
|
+
return `<${version}`
|
|
2494
|
+
case 'lte':
|
|
2495
|
+
return `<=${version}`
|
|
2496
|
+
case 'preserve': {
|
|
2497
|
+
const range = new vendor.semverExports.Range(refRange)
|
|
2498
|
+
const { raw } = range
|
|
2499
|
+
const comparators = [...range.set].flat()
|
|
2500
|
+
const { length } = comparators
|
|
2501
|
+
if (length === 1) {
|
|
2502
|
+
const char = /^[<>]=?/.exec(raw)?.[0]
|
|
2503
|
+
if (char) {
|
|
2504
|
+
return `${char}${version}`
|
|
2505
|
+
}
|
|
2506
|
+
} else if (length === 2) {
|
|
2507
|
+
const char = /^[~^]/.exec(raw)?.[0]
|
|
2508
|
+
if (char) {
|
|
2509
|
+
return `${char}${version}`
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
return version
|
|
2513
|
+
}
|
|
2514
|
+
case 'tilde':
|
|
2515
|
+
return `~${version}`
|
|
2516
|
+
case 'pin':
|
|
2517
|
+
default:
|
|
2518
|
+
return version
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
function getMajor(version) {
|
|
2522
|
+
const coerced = vendor.semverExports.coerce(version)
|
|
2523
|
+
if (coerced) {
|
|
2524
|
+
try {
|
|
2525
|
+
return vendor.semverExports.major(coerced)
|
|
2526
|
+
} catch (e) {
|
|
2527
|
+
debug.debugLog(`Error parsing '${version}':\n`, e)
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
return null
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2533
|
+
function extractPurlsFromPnpmLockfileV6(lockfile) {
|
|
2534
|
+
const deps = new Set()
|
|
2535
|
+
for (const importer of Object.values(lockfile.importers || {})) {
|
|
2536
|
+
if (importer.dependencies) {
|
|
2537
|
+
for (const { 0: alias, 1: ref } of Object.entries(
|
|
2538
|
+
importer.dependencies
|
|
2539
|
+
)) {
|
|
2540
|
+
const id = resolvePnpmPackageId(alias, ref)
|
|
2541
|
+
if (id) {
|
|
2542
|
+
deps.add(idToPurl(id))
|
|
2543
|
+
}
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
if (importer.devDependencies) {
|
|
2547
|
+
for (const { 0: alias, 1: ref } of Object.entries(
|
|
2548
|
+
importer.devDependencies
|
|
2549
|
+
)) {
|
|
2550
|
+
const id = resolvePnpmPackageId(alias, ref)
|
|
2551
|
+
if (id) {
|
|
2552
|
+
deps.add(idToPurl(id))
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
if (importer.optionalDependencies) {
|
|
2557
|
+
for (const { 0: alias, 1: ref } of Object.entries(
|
|
2558
|
+
importer.optionalDependencies
|
|
2559
|
+
)) {
|
|
2560
|
+
const id = resolvePnpmPackageId(alias, ref)
|
|
2561
|
+
if (id) {
|
|
2562
|
+
deps.add(idToPurl(id))
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
if (lockfile.packages) {
|
|
2568
|
+
for (const pkgPath of Object.keys(lockfile.packages)) {
|
|
2569
|
+
const id = resolvePnpmPackageIdFromPath(pkgPath, '')
|
|
2570
|
+
if (id) {
|
|
2571
|
+
deps.add(idToPurl(id))
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2575
|
+
return Array.from(deps)
|
|
2576
|
+
}
|
|
2577
|
+
function extractPurlsFromPnpmLockfileV9(lockfile) {
|
|
2578
|
+
const depTypes = vendor.libExports$2.detectDepTypes(lockfile)
|
|
2579
|
+
return Object.keys(depTypes).map(refId => {
|
|
2580
|
+
const purlObj = vendor.packageurlJsExports.PackageURL.fromString(
|
|
2581
|
+
idToPurl(refId)
|
|
2582
|
+
)
|
|
2583
|
+
const name = packages.resolvePackageName(purlObj)
|
|
2584
|
+
const version = resolvePackageVersion(purlObj)
|
|
2585
|
+
return idToPurl(`${name}@${version}`)
|
|
2586
|
+
})
|
|
2587
|
+
}
|
|
2588
|
+
function extractPurlsFromPnpmLockfile(lockfile) {
|
|
2589
|
+
return parsePnpmLockfileVersion(lockfile.lockfileVersion).major <= 6
|
|
2590
|
+
? extractPurlsFromPnpmLockfileV6(lockfile)
|
|
2591
|
+
: extractPurlsFromPnpmLockfileV9(lockfile)
|
|
2592
|
+
}
|
|
2593
|
+
function parsePnpmLockfileVersion(version) {
|
|
2594
|
+
return vendor.semverExports.coerce(version)
|
|
2595
|
+
}
|
|
2596
|
+
function resolvePnpmPackageId(alias, ref) {
|
|
2597
|
+
return ref.startsWith('/')
|
|
2598
|
+
? resolvePnpmPackageIdFromPath(ref, alias)
|
|
2599
|
+
: `${alias}@${stripPeerSuffix(ref)}`
|
|
2600
|
+
}
|
|
2601
|
+
function resolvePnpmPackageIdFromPath(ref, alias) {
|
|
2602
|
+
const relative = vendor.libExports$3.refToRelative(ref, alias)
|
|
2603
|
+
if (relative) {
|
|
2604
|
+
const id = stripLeadingSlash(relative)
|
|
2605
|
+
const purlObj = vendor.packageurlJsExports.PackageURL.fromString(
|
|
2606
|
+
idToPurl(id)
|
|
2607
|
+
)
|
|
2608
|
+
const name = packages.resolvePackageName(purlObj)
|
|
2609
|
+
const version = resolvePackageVersion(purlObj)
|
|
2610
|
+
return `${name}@${version}`
|
|
2611
|
+
}
|
|
2612
|
+
return null
|
|
2613
|
+
}
|
|
2614
|
+
|
|
2615
|
+
async function getAlertsMapFromPnpmLockfile(lockfile, options_) {
|
|
2616
|
+
const options = {
|
|
2617
|
+
__proto__: null,
|
|
2618
|
+
consolidate: false,
|
|
2619
|
+
limit: Infinity,
|
|
2620
|
+
nothrow: false,
|
|
2621
|
+
...options_
|
|
2622
|
+
}
|
|
2623
|
+
const purls = extractPurlsFromPnpmLockfile(lockfile)
|
|
2624
|
+
return await getAlertsMapFromPurls(purls, {
|
|
2625
|
+
overrides: lockfile.overrides,
|
|
2626
|
+
...options
|
|
2627
|
+
})
|
|
2628
|
+
}
|
|
2629
|
+
async function getAlertsMapFromPurls(purls, options_) {
|
|
2630
|
+
const options = {
|
|
2631
|
+
__proto__: null,
|
|
2632
|
+
consolidate: false,
|
|
2633
|
+
nothrow: false,
|
|
2634
|
+
...options_
|
|
2635
|
+
}
|
|
2636
|
+
const include = {
|
|
2637
|
+
__proto__: null,
|
|
2638
|
+
actions: undefined,
|
|
2639
|
+
blocked: true,
|
|
2640
|
+
critical: true,
|
|
2641
|
+
cve: true,
|
|
2642
|
+
existing: false,
|
|
2643
|
+
unfixable: true,
|
|
2644
|
+
upgradable: false,
|
|
2645
|
+
...options.include
|
|
2646
|
+
}
|
|
2647
|
+
const { spinner } = options
|
|
2648
|
+
const uniqPurls = arrays.arrayUnique(purls)
|
|
2649
|
+
let { length: remaining } = uniqPurls
|
|
2650
|
+
const alertsByPkgId = new Map()
|
|
2651
|
+
if (!remaining) {
|
|
2652
|
+
return alertsByPkgId
|
|
2653
|
+
}
|
|
2654
|
+
const getText = () => `Looking up data for ${remaining} packages`
|
|
2655
|
+
spinner?.start(getText())
|
|
2656
|
+
const sockSdkResult = await setupSdk(getPublicToken())
|
|
2657
|
+
if (!sockSdkResult.ok) {
|
|
2658
|
+
throw new Error('Auth error: Try to run `socket login` first')
|
|
2659
|
+
}
|
|
2660
|
+
const sockSdk = sockSdkResult.data
|
|
2661
|
+
const toAlertsMapOptions = {
|
|
2662
|
+
overrides: options.overrides,
|
|
2663
|
+
consolidate: options.consolidate,
|
|
2664
|
+
include,
|
|
2665
|
+
spinner
|
|
2666
|
+
}
|
|
2667
|
+
for await (const batchResult of sockSdk.batchPackageStream(
|
|
2668
|
+
{
|
|
2669
|
+
alerts: 'true',
|
|
2670
|
+
compact: 'true',
|
|
2671
|
+
...(include.actions
|
|
2672
|
+
? {
|
|
2673
|
+
actions: include.actions.join(',')
|
|
2674
|
+
}
|
|
2675
|
+
: {}),
|
|
2676
|
+
...(include.unfixable
|
|
2677
|
+
? {}
|
|
2678
|
+
: {
|
|
2679
|
+
fixable: 'true'
|
|
2680
|
+
})
|
|
2681
|
+
},
|
|
2682
|
+
{
|
|
2683
|
+
components: uniqPurls.map(purl => ({
|
|
2684
|
+
purl
|
|
2685
|
+
}))
|
|
2686
|
+
}
|
|
2687
|
+
)) {
|
|
2688
|
+
if (batchResult.success) {
|
|
2689
|
+
await addArtifactToAlertsMap(
|
|
2690
|
+
batchResult.data,
|
|
2691
|
+
alertsByPkgId,
|
|
2692
|
+
toAlertsMapOptions
|
|
2693
|
+
)
|
|
2694
|
+
} else if (!options.nothrow) {
|
|
2695
|
+
const statusCode = batchResult.status ?? 'unknown'
|
|
2696
|
+
const statusMessage = batchResult.error ?? 'No status message'
|
|
2697
|
+
throw new Error(
|
|
2698
|
+
`Socket API server error (${statusCode}): ${statusMessage}`
|
|
2699
|
+
)
|
|
2700
|
+
}
|
|
2701
|
+
remaining -= 1
|
|
2702
|
+
if (spinner && remaining > 0) {
|
|
2703
|
+
spinner.start()
|
|
2704
|
+
spinner.setText(getText())
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
spinner?.stop()
|
|
2708
|
+
return alertsByPkgId
|
|
2709
|
+
}
|
|
2710
|
+
|
|
2711
|
+
const {
|
|
2712
|
+
NPM: NPM$2,
|
|
2713
|
+
SOCKET_CLI_SAFE_BIN,
|
|
2714
|
+
SOCKET_CLI_SAFE_PROGRESS,
|
|
2715
|
+
SOCKET_IPC_HANDSHAKE
|
|
2716
|
+
} = constants
|
|
2717
|
+
function safeNpmInstall(options) {
|
|
2718
|
+
const {
|
|
2719
|
+
agentExecPath = getNpmBinPath(),
|
|
2720
|
+
args = [],
|
|
2721
|
+
ipc,
|
|
2722
|
+
spinner,
|
|
2723
|
+
...spawnOptions
|
|
2724
|
+
} = {
|
|
2725
|
+
__proto__: null,
|
|
2726
|
+
...options
|
|
2727
|
+
}
|
|
2728
|
+
let stdio = spawnOptions.stdio
|
|
2729
|
+
const useIpc = objects.isObject(ipc)
|
|
2730
|
+
// Include 'ipc' in the spawnOptions.stdio when an options.ipc object is provided.
|
|
2731
|
+
// See https://github.com/nodejs/node/blob/v23.6.0/lib/child_process.js#L161-L166
|
|
2732
|
+
// and https://github.com/nodejs/node/blob/v23.6.0/lib/internal/child_process.js#L238.
|
|
2733
|
+
if (typeof stdio === 'string') {
|
|
2734
|
+
stdio = useIpc ? [stdio, stdio, stdio, 'ipc'] : [stdio, stdio, stdio]
|
|
2735
|
+
} else if (useIpc && Array.isArray(stdio) && !stdio.includes('ipc')) {
|
|
2736
|
+
stdio = stdio.concat('ipc')
|
|
2737
|
+
}
|
|
2738
|
+
const useDebug = debug.isDebug()
|
|
2739
|
+
const terminatorPos = args.indexOf('--')
|
|
2740
|
+
const rawBinArgs = terminatorPos === -1 ? args : args.slice(0, terminatorPos)
|
|
2741
|
+
const progressArg =
|
|
2742
|
+
rawBinArgs.findLast(npm.isProgressFlag) !== '--no-progress'
|
|
2743
|
+
const binArgs = rawBinArgs.filter(
|
|
2744
|
+
a => !npm.isAuditFlag(a) && !npm.isFundFlag(a) && !npm.isProgressFlag(a)
|
|
2745
|
+
)
|
|
2746
|
+
const otherArgs = terminatorPos === -1 ? [] : args.slice(terminatorPos)
|
|
2747
|
+
const isSilent = !useDebug && !binArgs.some(npm.isLoglevelFlag)
|
|
2748
|
+
const logLevelArgs = isSilent ? ['--loglevel', 'silent'] : []
|
|
2749
|
+
const spawnPromise = spawn.spawn(
|
|
2750
|
+
// Lazily access constants.execPath.
|
|
2751
|
+
constants.execPath,
|
|
2752
|
+
[
|
|
2753
|
+
// Lazily access constants.nodeHardenFlags.
|
|
2754
|
+
...constants.nodeHardenFlags,
|
|
2755
|
+
// Lazily access constants.nodeNoWarningsFlags.
|
|
2756
|
+
...constants.nodeNoWarningsFlags,
|
|
2757
|
+
// Lazily access constants.ENV.INLINED_SOCKET_CLI_SENTRY_BUILD.
|
|
2758
|
+
...(constants.ENV.INLINED_SOCKET_CLI_SENTRY_BUILD
|
|
2759
|
+
? [
|
|
2760
|
+
'--require',
|
|
2761
|
+
// Lazily access constants.distInstrumentWithSentryPath.
|
|
2762
|
+
constants.distInstrumentWithSentryPath
|
|
2763
|
+
]
|
|
2764
|
+
: []),
|
|
2765
|
+
'--require',
|
|
2766
|
+
// Lazily access constants.distShadowNpmInjectPath.
|
|
2767
|
+
constants.distShadowNpmInjectPath,
|
|
2768
|
+
npm.realExecPathSync(agentExecPath),
|
|
2769
|
+
'install',
|
|
2770
|
+
// Avoid code paths for 'audit' and 'fund'.
|
|
2771
|
+
'--no-audit',
|
|
2772
|
+
'--no-fund',
|
|
2773
|
+
// Add '--no-progress' to fix input being swallowed by the npm spinner.
|
|
2774
|
+
'--no-progress',
|
|
2775
|
+
// Add '--loglevel=silent' if a loglevel flag is not provided and the
|
|
2776
|
+
// SOCKET_CLI_DEBUG environment variable is not truthy.
|
|
2777
|
+
...logLevelArgs,
|
|
2778
|
+
...binArgs,
|
|
2779
|
+
...otherArgs
|
|
2780
|
+
],
|
|
2781
|
+
{
|
|
2782
|
+
spinner,
|
|
2783
|
+
...spawnOptions,
|
|
2784
|
+
stdio,
|
|
2785
|
+
env: {
|
|
2786
|
+
...process.env,
|
|
2787
|
+
...spawnOptions.env
|
|
2788
|
+
}
|
|
2789
|
+
}
|
|
2790
|
+
)
|
|
2791
|
+
if (useIpc) {
|
|
2792
|
+
spawnPromise.process.send({
|
|
2793
|
+
[SOCKET_IPC_HANDSHAKE]: {
|
|
2794
|
+
[SOCKET_CLI_SAFE_BIN]: NPM$2,
|
|
2795
|
+
[SOCKET_CLI_SAFE_PROGRESS]: progressArg,
|
|
2796
|
+
...ipc
|
|
2797
|
+
}
|
|
2798
|
+
})
|
|
2799
|
+
}
|
|
2800
|
+
return spawnPromise
|
|
2801
|
+
}
|
|
2802
|
+
|
|
2803
|
+
const { NPM: NPM$1, PNPM: PNPM$1 } = constants
|
|
2804
|
+
function runAgentInstall(pkgEnvDetails, options) {
|
|
2805
|
+
const { agent, agentExecPath } = pkgEnvDetails
|
|
2806
|
+
// All package managers support the "install" command.
|
|
2807
|
+
if (agent === NPM$1) {
|
|
2808
|
+
return safeNpmInstall({
|
|
2809
|
+
agentExecPath,
|
|
2810
|
+
...options
|
|
2811
|
+
})
|
|
2812
|
+
}
|
|
2813
|
+
const {
|
|
2814
|
+
args = [],
|
|
2815
|
+
spinner,
|
|
2816
|
+
...spawnOptions
|
|
2817
|
+
} = {
|
|
2818
|
+
__proto__: null,
|
|
2819
|
+
...options
|
|
2820
|
+
}
|
|
2821
|
+
const skipNodeHardenFlags =
|
|
2822
|
+
agent === PNPM$1 && pkgEnvDetails.agentVersion.major < 11
|
|
2823
|
+
return spawn.spawn(agentExecPath, ['install', ...args], {
|
|
2824
|
+
// Lazily access constants.WIN32.
|
|
2825
|
+
shell: constants.WIN32,
|
|
2826
|
+
spinner,
|
|
2827
|
+
stdio: 'inherit',
|
|
2828
|
+
...spawnOptions,
|
|
2829
|
+
env: {
|
|
2830
|
+
...process.env,
|
|
2831
|
+
NODE_OPTIONS: cmdFlagsToString([
|
|
2832
|
+
...(skipNodeHardenFlags
|
|
2833
|
+
? []
|
|
2834
|
+
: // Lazily access constants.nodeHardenFlags.
|
|
2835
|
+
constants.nodeHardenFlags),
|
|
2836
|
+
// Lazily access constants.nodeNoWarningsFlags.
|
|
2837
|
+
...constants.nodeNoWarningsFlags
|
|
2838
|
+
]),
|
|
2839
|
+
...spawnOptions.env
|
|
2840
|
+
}
|
|
2841
|
+
})
|
|
2842
|
+
}
|
|
2843
|
+
|
|
2844
|
+
const {
|
|
2845
|
+
BINARY_LOCK_EXT,
|
|
2846
|
+
BUN,
|
|
2847
|
+
HIDDEN_PACKAGE_LOCK_JSON,
|
|
2848
|
+
LOCK_EXT,
|
|
2849
|
+
NPM,
|
|
2850
|
+
NPM_BUGGY_OVERRIDES_PATCHED_VERSION,
|
|
2851
|
+
PACKAGE_JSON,
|
|
2852
|
+
PNPM,
|
|
2853
|
+
VLT,
|
|
2854
|
+
YARN,
|
|
2855
|
+
YARN_BERRY,
|
|
2856
|
+
YARN_CLASSIC
|
|
2857
|
+
} = constants
|
|
2858
|
+
const AGENTS = new Set([BUN, NPM, PNPM, YARN_BERRY, YARN_CLASSIC, VLT])
|
|
2859
|
+
const binByAgent = new Map([
|
|
2860
|
+
[BUN, BUN],
|
|
2861
|
+
[NPM, NPM],
|
|
2862
|
+
[PNPM, PNPM],
|
|
2863
|
+
[YARN_BERRY, YARN],
|
|
2864
|
+
[YARN_CLASSIC, YARN],
|
|
2865
|
+
[VLT, VLT]
|
|
2866
|
+
])
|
|
2867
|
+
async function getAgentExecPath(agent) {
|
|
2868
|
+
const binName = binByAgent.get(agent)
|
|
2869
|
+
if (binName === NPM) {
|
|
2870
|
+
// Lazily access constants.npmExecPath.
|
|
2871
|
+
return constants.npmExecPath
|
|
2872
|
+
}
|
|
2873
|
+
return (
|
|
2874
|
+
(await vendor.libExports$1(binName, {
|
|
2875
|
+
nothrow: true
|
|
2876
|
+
})) ?? binName
|
|
2877
|
+
)
|
|
2878
|
+
}
|
|
2879
|
+
async function getAgentVersion(agentExecPath, cwd) {
|
|
2880
|
+
let result
|
|
2881
|
+
try {
|
|
2882
|
+
result =
|
|
2883
|
+
// Coerce version output into a valid semver version by passing it through
|
|
2884
|
+
// semver.coerce which strips leading v's, carets (^), comparators (<,<=,>,>=,=),
|
|
2885
|
+
// and tildes (~).
|
|
2886
|
+
vendor.semverExports.coerce(
|
|
2887
|
+
// All package managers support the "--version" flag.
|
|
2888
|
+
(
|
|
2889
|
+
await spawn.spawn(agentExecPath, ['--version'], {
|
|
2890
|
+
cwd,
|
|
2891
|
+
// Lazily access constants.WIN32.
|
|
2892
|
+
shell: constants.WIN32
|
|
2893
|
+
})
|
|
2894
|
+
).stdout
|
|
2895
|
+
) ?? undefined
|
|
2896
|
+
} catch (e) {
|
|
2897
|
+
debug.debugLog('getAgentVersion error:\n', e)
|
|
2898
|
+
}
|
|
2899
|
+
return result
|
|
2900
|
+
}
|
|
2901
|
+
|
|
2902
|
+
// The order of LOCKS properties IS significant as it affects iteration order.
|
|
2903
|
+
const LOCKS = {
|
|
2904
|
+
[`bun${LOCK_EXT}`]: BUN,
|
|
2905
|
+
[`bun${BINARY_LOCK_EXT}`]: BUN,
|
|
2906
|
+
// If both package-lock.json and npm-shrinkwrap.json are present in the root
|
|
2907
|
+
// of a project, npm-shrinkwrap.json will take precedence and package-lock.json
|
|
2908
|
+
// will be ignored.
|
|
2909
|
+
// https://docs.npmjs.com/cli/v10/configuring-npm/package-lock-json#package-lockjson-vs-npm-shrinkwrapjson
|
|
2910
|
+
'npm-shrinkwrap.json': NPM,
|
|
2911
|
+
'package-lock.json': NPM,
|
|
2912
|
+
'pnpm-lock.yaml': PNPM,
|
|
2913
|
+
'pnpm-lock.yml': PNPM,
|
|
2914
|
+
[`yarn${LOCK_EXT}`]: YARN_CLASSIC,
|
|
2915
|
+
'vlt-lock.json': VLT,
|
|
2916
|
+
// Lastly, look for a hidden lock file which is present if .npmrc has package-lock=false:
|
|
2917
|
+
// https://docs.npmjs.com/cli/v10/configuring-npm/package-lock-json#hidden-lockfiles
|
|
2918
|
+
//
|
|
2919
|
+
// Unlike the other LOCKS keys this key contains a directory AND filename so
|
|
2920
|
+
// it has to be handled differently.
|
|
2921
|
+
'node_modules/.package-lock.json': NPM
|
|
2922
|
+
}
|
|
2923
|
+
const readLockFileByAgent = (() => {
|
|
2924
|
+
function wrapReader(reader) {
|
|
2925
|
+
return async (...args) => {
|
|
2926
|
+
try {
|
|
2927
|
+
return await reader(...args)
|
|
2928
|
+
} catch {}
|
|
2929
|
+
return undefined
|
|
2930
|
+
}
|
|
2931
|
+
}
|
|
2932
|
+
const binaryReader = wrapReader(readFileBinary)
|
|
2933
|
+
const defaultReader = wrapReader(
|
|
2934
|
+
async lockPath => await readFileUtf8(lockPath)
|
|
2935
|
+
)
|
|
2936
|
+
return new Map([
|
|
2937
|
+
[
|
|
2938
|
+
BUN,
|
|
2939
|
+
wrapReader(async (lockPath, agentExecPath, cwd = process.cwd()) => {
|
|
2940
|
+
const ext = path.extname(lockPath)
|
|
2941
|
+
if (ext === LOCK_EXT) {
|
|
2942
|
+
return await defaultReader(lockPath)
|
|
2943
|
+
}
|
|
2944
|
+
if (ext === BINARY_LOCK_EXT) {
|
|
2945
|
+
const lockBuffer = await binaryReader(lockPath)
|
|
2946
|
+
if (lockBuffer) {
|
|
2947
|
+
try {
|
|
2948
|
+
return vendor.hyrious__bun_lockbExports.parse(lockBuffer)
|
|
2949
|
+
} catch {}
|
|
2950
|
+
}
|
|
2951
|
+
// To print a Yarn lockfile to your console without writing it to disk
|
|
2952
|
+
// use `bun bun.lockb`.
|
|
2953
|
+
// https://bun.sh/guides/install/yarnlock
|
|
2954
|
+
return (
|
|
2955
|
+
await spawn.spawn(agentExecPath, [lockPath], {
|
|
2956
|
+
cwd,
|
|
2957
|
+
// Lazily access constants.WIN32.
|
|
2958
|
+
shell: constants.WIN32
|
|
2959
|
+
})
|
|
2960
|
+
).stdout.trim()
|
|
2961
|
+
}
|
|
2962
|
+
return undefined
|
|
2963
|
+
})
|
|
2964
|
+
],
|
|
2965
|
+
[NPM, defaultReader],
|
|
2966
|
+
[PNPM, defaultReader],
|
|
2967
|
+
[VLT, defaultReader],
|
|
2968
|
+
[YARN_BERRY, defaultReader],
|
|
2969
|
+
[YARN_CLASSIC, defaultReader]
|
|
2970
|
+
])
|
|
2971
|
+
})()
|
|
2972
|
+
async function detectPackageEnvironment({
|
|
2973
|
+
cwd = process.cwd(),
|
|
2974
|
+
onUnknown
|
|
2975
|
+
} = {}) {
|
|
2976
|
+
let lockPath = await findUp(Object.keys(LOCKS), {
|
|
2977
|
+
cwd
|
|
2978
|
+
})
|
|
2979
|
+
let lockName = lockPath ? path.basename(lockPath) : undefined
|
|
2980
|
+
const isHiddenLockFile = lockName === HIDDEN_PACKAGE_LOCK_JSON
|
|
2981
|
+
const pkgJsonPath = lockPath
|
|
2982
|
+
? path.resolve(
|
|
2983
|
+
lockPath,
|
|
2984
|
+
`${isHiddenLockFile ? '../' : ''}../${PACKAGE_JSON}`
|
|
2985
|
+
)
|
|
2986
|
+
: await findUp(PACKAGE_JSON, {
|
|
2987
|
+
cwd
|
|
2988
|
+
})
|
|
2989
|
+
const pkgPath =
|
|
2990
|
+
pkgJsonPath && fs.existsSync(pkgJsonPath)
|
|
2991
|
+
? path.dirname(pkgJsonPath)
|
|
2992
|
+
: undefined
|
|
2993
|
+
const editablePkgJson = pkgPath
|
|
2994
|
+
? await packages.readPackageJson(pkgPath, {
|
|
2995
|
+
editable: true
|
|
2996
|
+
})
|
|
2997
|
+
: undefined
|
|
2998
|
+
// Read Corepack `packageManager` field in package.json:
|
|
2999
|
+
// https://nodejs.org/api/packages.html#packagemanager
|
|
3000
|
+
const pkgManager = strings.isNonEmptyString(
|
|
3001
|
+
editablePkgJson?.content?.packageManager
|
|
3002
|
+
)
|
|
3003
|
+
? editablePkgJson.content.packageManager
|
|
3004
|
+
: undefined
|
|
3005
|
+
let agent
|
|
3006
|
+
if (pkgManager) {
|
|
3007
|
+
// A valid "packageManager" field value is "<package manager name>@<version>".
|
|
3008
|
+
// https://nodejs.org/api/packages.html#packagemanager
|
|
3009
|
+
const atSignIndex = pkgManager.lastIndexOf('@')
|
|
3010
|
+
if (atSignIndex !== -1) {
|
|
3011
|
+
const name = pkgManager.slice(0, atSignIndex)
|
|
3012
|
+
const version = pkgManager.slice(atSignIndex + 1)
|
|
3013
|
+
if (version && AGENTS.has(name)) {
|
|
3014
|
+
agent = name
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
3017
|
+
}
|
|
3018
|
+
if (
|
|
3019
|
+
agent === undefined &&
|
|
3020
|
+
!isHiddenLockFile &&
|
|
3021
|
+
typeof pkgJsonPath === 'string' &&
|
|
3022
|
+
typeof lockName === 'string'
|
|
3023
|
+
) {
|
|
3024
|
+
agent = LOCKS[lockName]
|
|
3025
|
+
}
|
|
3026
|
+
if (agent === undefined) {
|
|
3027
|
+
agent = NPM
|
|
3028
|
+
onUnknown?.(pkgManager)
|
|
3029
|
+
}
|
|
3030
|
+
const agentExecPath = await getAgentExecPath(agent)
|
|
3031
|
+
const agentVersion = await getAgentVersion(agentExecPath, cwd)
|
|
3032
|
+
if (agent === YARN_CLASSIC && (agentVersion?.major ?? 0) > 1) {
|
|
3033
|
+
agent = YARN_BERRY
|
|
3034
|
+
}
|
|
3035
|
+
// Lazily access constants.maintainedNodeVersions.
|
|
3036
|
+
const { maintainedNodeVersions } = constants
|
|
3037
|
+
// Lazily access constants.minimumVersionByAgent.
|
|
3038
|
+
const minSupportedAgentVersion = constants.minimumVersionByAgent.get(agent)
|
|
3039
|
+
const minSupportedNodeVersion = maintainedNodeVersions.last
|
|
3040
|
+
const nodeVersion = vendor.semverExports.coerce(process.version)
|
|
3041
|
+
let lockSrc
|
|
3042
|
+
let pkgAgentRange
|
|
3043
|
+
let pkgNodeRange
|
|
3044
|
+
let pkgMinAgentVersion = minSupportedAgentVersion
|
|
3045
|
+
let pkgMinNodeVersion = minSupportedNodeVersion
|
|
3046
|
+
if (editablePkgJson?.content) {
|
|
3047
|
+
const { engines } = editablePkgJson.content
|
|
3048
|
+
const engineAgentRange = engines?.[agent]
|
|
3049
|
+
const engineNodeRange = engines?.['node']
|
|
3050
|
+
if (strings.isNonEmptyString(engineAgentRange)) {
|
|
3051
|
+
pkgAgentRange = engineAgentRange
|
|
3052
|
+
// Roughly check agent range as semver.coerce will strip leading
|
|
3053
|
+
// v's, carets (^), comparators (<,<=,>,>=,=), and tildes (~).
|
|
3054
|
+
const coerced = vendor.semverExports.coerce(pkgAgentRange)
|
|
3055
|
+
if (coerced && vendor.semverExports.lt(coerced, pkgMinAgentVersion)) {
|
|
3056
|
+
pkgMinAgentVersion = coerced.version
|
|
3057
|
+
}
|
|
3058
|
+
}
|
|
3059
|
+
if (strings.isNonEmptyString(engineNodeRange)) {
|
|
3060
|
+
pkgNodeRange = engineNodeRange
|
|
3061
|
+
// Roughly check Node range as semver.coerce will strip leading
|
|
3062
|
+
// v's, carets (^), comparators (<,<=,>,>=,=), and tildes (~).
|
|
3063
|
+
const coerced = vendor.semverExports.coerce(pkgNodeRange)
|
|
3064
|
+
if (coerced && vendor.semverExports.lt(coerced, pkgMinNodeVersion)) {
|
|
3065
|
+
pkgMinNodeVersion = coerced.version
|
|
3066
|
+
}
|
|
3067
|
+
}
|
|
3068
|
+
const browserslistQuery = editablePkgJson.content['browserslist']
|
|
3069
|
+
if (Array.isArray(browserslistQuery)) {
|
|
3070
|
+
// List Node targets in ascending version order.
|
|
3071
|
+
const browserslistNodeTargets = vendor
|
|
3072
|
+
.browserslistExports(browserslistQuery)
|
|
3073
|
+
.filter(v => /^node /i.test(v))
|
|
3074
|
+
.map(v => v.slice(5 /*'node '.length*/))
|
|
3075
|
+
.sort(sorts.naturalCompare)
|
|
3076
|
+
if (browserslistNodeTargets.length) {
|
|
3077
|
+
// browserslistNodeTargets[0] is the lowest Node target version.
|
|
3078
|
+
const coerced = vendor.semverExports.coerce(browserslistNodeTargets[0])
|
|
3079
|
+
if (coerced && vendor.semverExports.lt(coerced, pkgMinNodeVersion)) {
|
|
3080
|
+
pkgMinNodeVersion = coerced.version
|
|
3081
|
+
}
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
lockSrc =
|
|
3085
|
+
typeof lockPath === 'string'
|
|
3086
|
+
? await readLockFileByAgent.get(agent)(lockPath, agentExecPath, cwd)
|
|
3087
|
+
: undefined
|
|
3088
|
+
} else {
|
|
3089
|
+
lockName = undefined
|
|
3090
|
+
lockPath = undefined
|
|
3091
|
+
}
|
|
3092
|
+
// Does the system agent version meet our minimum supported agent version?
|
|
3093
|
+
const agentSupported =
|
|
3094
|
+
!!agentVersion &&
|
|
3095
|
+
vendor.semverExports.satisfies(
|
|
3096
|
+
agentVersion,
|
|
3097
|
+
`>=${minSupportedAgentVersion}`
|
|
3098
|
+
)
|
|
3099
|
+
|
|
3100
|
+
// Does the system Node version meet our minimum supported Node version?
|
|
3101
|
+
const nodeSupported = vendor.semverExports.satisfies(
|
|
3102
|
+
nodeVersion,
|
|
3103
|
+
`>=${minSupportedNodeVersion}`
|
|
3104
|
+
)
|
|
3105
|
+
const npmExecPath =
|
|
3106
|
+
agent === NPM ? agentExecPath : await getAgentExecPath(NPM)
|
|
3107
|
+
const npmBuggyOverrides =
|
|
3108
|
+
agent === NPM &&
|
|
3109
|
+
!!agentVersion &&
|
|
3110
|
+
vendor.semverExports.lt(agentVersion, NPM_BUGGY_OVERRIDES_PATCHED_VERSION)
|
|
3111
|
+
return {
|
|
3112
|
+
agent,
|
|
3113
|
+
agentExecPath,
|
|
3114
|
+
agentSupported,
|
|
3115
|
+
agentVersion,
|
|
3116
|
+
editablePkgJson,
|
|
3117
|
+
features: {
|
|
3118
|
+
npmBuggyOverrides
|
|
3119
|
+
},
|
|
3120
|
+
lockName,
|
|
3121
|
+
lockPath,
|
|
3122
|
+
lockSrc,
|
|
3123
|
+
nodeSupported,
|
|
3124
|
+
nodeVersion,
|
|
3125
|
+
npmExecPath,
|
|
3126
|
+
pkgPath,
|
|
3127
|
+
pkgRequirements: {
|
|
3128
|
+
agent: pkgAgentRange ?? `>=${pkgMinAgentVersion}`,
|
|
3129
|
+
node: pkgNodeRange ?? `>=${pkgMinNodeVersion}`
|
|
3130
|
+
},
|
|
3131
|
+
pkgSupports: {
|
|
3132
|
+
// Does our minimum supported agent version meet the package's requirements?
|
|
3133
|
+
agent: vendor.semverExports.satisfies(
|
|
3134
|
+
minSupportedAgentVersion,
|
|
3135
|
+
`>=${pkgMinAgentVersion}`
|
|
3136
|
+
),
|
|
3137
|
+
// Does our supported Node versions meet the package's requirements?
|
|
3138
|
+
node: maintainedNodeVersions.some(v =>
|
|
3139
|
+
vendor.semverExports.satisfies(v, `>=${pkgMinNodeVersion}`)
|
|
3140
|
+
)
|
|
3141
|
+
}
|
|
3142
|
+
}
|
|
3143
|
+
}
|
|
3144
|
+
async function detectAndValidatePackageEnvironment(cwd, options) {
|
|
3145
|
+
const {
|
|
3146
|
+
cmdName = '',
|
|
3147
|
+
logger,
|
|
3148
|
+
prod
|
|
3149
|
+
} = {
|
|
3150
|
+
__proto__: null,
|
|
3151
|
+
...options
|
|
3152
|
+
}
|
|
3153
|
+
const details = await detectPackageEnvironment({
|
|
3154
|
+
cwd,
|
|
3155
|
+
onUnknown(pkgManager) {
|
|
3156
|
+
logger?.warn(
|
|
3157
|
+
cmdPrefixMessage(
|
|
3158
|
+
cmdName,
|
|
3159
|
+
`Unknown package manager${pkgManager ? ` ${pkgManager}` : ''}, defaulting to npm`
|
|
3160
|
+
)
|
|
3161
|
+
)
|
|
3162
|
+
}
|
|
3163
|
+
})
|
|
3164
|
+
const { agent, nodeVersion, pkgRequirements } = details
|
|
3165
|
+
const agentVersion = details.agentVersion ?? 'unknown'
|
|
3166
|
+
if (!details.agentSupported) {
|
|
3167
|
+
const minVersion = constants.minimumVersionByAgent.get(agent)
|
|
3168
|
+
logger?.fail(
|
|
3169
|
+
cmdPrefixMessage(
|
|
3170
|
+
cmdName,
|
|
3171
|
+
`Requires ${agent} >=${minVersion}. Current version: ${agentVersion}.`
|
|
3172
|
+
)
|
|
3173
|
+
)
|
|
3174
|
+
return
|
|
3175
|
+
}
|
|
3176
|
+
if (!details.nodeSupported) {
|
|
3177
|
+
const minVersion = constants.maintainedNodeVersions.last
|
|
3178
|
+
logger?.fail(
|
|
3179
|
+
cmdPrefixMessage(
|
|
3180
|
+
cmdName,
|
|
3181
|
+
`Requires Node >=${minVersion}. Current version: ${nodeVersion}.`
|
|
3182
|
+
)
|
|
3183
|
+
)
|
|
3184
|
+
return
|
|
3185
|
+
}
|
|
3186
|
+
if (!details.pkgSupports.agent) {
|
|
3187
|
+
logger?.fail(
|
|
3188
|
+
cmdPrefixMessage(
|
|
3189
|
+
cmdName,
|
|
3190
|
+
`Package engine "${agent}" requires ${pkgRequirements.agent}. Current version: ${agentVersion}`
|
|
3191
|
+
)
|
|
3192
|
+
)
|
|
3193
|
+
return
|
|
3194
|
+
}
|
|
3195
|
+
if (!details.pkgSupports.node) {
|
|
3196
|
+
logger?.fail(
|
|
3197
|
+
cmdPrefixMessage(
|
|
3198
|
+
cmdName,
|
|
3199
|
+
`Package engine "node" requires ${pkgRequirements.node}. Current version: ${nodeVersion}`
|
|
3200
|
+
)
|
|
3201
|
+
)
|
|
3202
|
+
return
|
|
3203
|
+
}
|
|
3204
|
+
if (agent === VLT) {
|
|
3205
|
+
logger?.fail(
|
|
3206
|
+
cmdPrefixMessage(
|
|
3207
|
+
cmdName,
|
|
3208
|
+
`${agent} does not support overrides. Soon, though ⚡`
|
|
3209
|
+
)
|
|
3210
|
+
)
|
|
3211
|
+
return
|
|
3212
|
+
}
|
|
3213
|
+
const lockName = details.lockName ?? 'lock file'
|
|
3214
|
+
if (details.lockName === undefined || details.lockSrc === undefined) {
|
|
3215
|
+
logger?.fail(cmdPrefixMessage(cmdName, `No ${lockName} found`))
|
|
3216
|
+
return
|
|
3217
|
+
}
|
|
3218
|
+
if (details.lockSrc.trim() === '') {
|
|
3219
|
+
logger?.fail(cmdPrefixMessage(cmdName, `${lockName} is empty`))
|
|
3220
|
+
return
|
|
3221
|
+
}
|
|
3222
|
+
if (details.pkgPath === undefined) {
|
|
3223
|
+
logger?.fail(cmdPrefixMessage(cmdName, `No ${PACKAGE_JSON} found`))
|
|
3224
|
+
return
|
|
3225
|
+
}
|
|
3226
|
+
if (prod && (agent === BUN || agent === YARN_BERRY)) {
|
|
3227
|
+
logger?.fail(
|
|
3228
|
+
cmdPrefixMessage(
|
|
3229
|
+
cmdName,
|
|
3230
|
+
`--prod not supported for ${agent}${agentVersion ? `@${agentVersion}` : ''}`
|
|
3231
|
+
)
|
|
3232
|
+
)
|
|
3233
|
+
return
|
|
3234
|
+
}
|
|
3235
|
+
if (
|
|
3236
|
+
details.lockPath &&
|
|
3237
|
+
path.relative(cwd, details.lockPath).startsWith('.')
|
|
3238
|
+
) {
|
|
3239
|
+
// Note: In tests we return <redacted> because otherwise snapshots will fail.
|
|
3240
|
+
const { REDACTED } = constants
|
|
3241
|
+
// Lazily access constants.ENV.VITEST.
|
|
3242
|
+
const redacting = constants.ENV.VITEST
|
|
3243
|
+
logger?.warn(
|
|
3244
|
+
cmdPrefixMessage(
|
|
3245
|
+
cmdName,
|
|
3246
|
+
`Package ${lockName} found at ${redacting ? REDACTED : details.lockPath}`
|
|
3247
|
+
)
|
|
3248
|
+
)
|
|
3249
|
+
}
|
|
3250
|
+
return details
|
|
3251
|
+
}
|
|
3252
|
+
|
|
3253
|
+
exports.ALERT_SEVERITY = ALERT_SEVERITY
|
|
3254
|
+
exports.AuthError = AuthError
|
|
3255
|
+
exports.ColorOrMarkdown = ColorOrMarkdown
|
|
3256
|
+
exports.InputError = InputError
|
|
3257
|
+
exports.RangeStyles = RangeStyles
|
|
3258
|
+
exports.applyRange = applyRange
|
|
3259
|
+
exports.captureException = captureException
|
|
3260
|
+
exports.checkCommandInput = checkCommandInput
|
|
3261
|
+
exports.cmdPrefixMessage = cmdPrefixMessage
|
|
3262
|
+
exports.commonFlags = commonFlags
|
|
3263
|
+
exports.createEnum = createEnum
|
|
3264
|
+
exports.detectAndValidatePackageEnvironment =
|
|
3265
|
+
detectAndValidatePackageEnvironment
|
|
3266
|
+
exports.determineOrgSlug = determineOrgSlug
|
|
3267
|
+
exports.failMsgWithBadge = failMsgWithBadge
|
|
3268
|
+
exports.formatSeverityCount = formatSeverityCount
|
|
3269
|
+
exports.getAlertsMapFromPnpmLockfile = getAlertsMapFromPnpmLockfile
|
|
3270
|
+
exports.getAlertsMapFromPurls = getAlertsMapFromPurls
|
|
3271
|
+
exports.getConfigValue = getConfigValue
|
|
3272
|
+
exports.getConfigValueOrUndef = getConfigValueOrUndef
|
|
3273
|
+
exports.getCveInfoByAlertsMap = getCveInfoByAlertsMap
|
|
3274
|
+
exports.getFlagListOutput = getFlagListOutput
|
|
3275
|
+
exports.getMajor = getMajor
|
|
3276
|
+
exports.getNpmBinPath = getNpmBinPath
|
|
3277
|
+
exports.getNpmRequire = getNpmRequire
|
|
3278
|
+
exports.getNpxBinPath = getNpxBinPath
|
|
3279
|
+
exports.getOutputKind = getOutputKind
|
|
3280
|
+
exports.getPackageFilesForScan = getPackageFilesForScan
|
|
3281
|
+
exports.getPkgFullNameFromPurlObj = getPkgFullNameFromPurlObj
|
|
3282
|
+
exports.getPublicToken = getPublicToken
|
|
3283
|
+
exports.getSeverityCount = getSeverityCount
|
|
3284
|
+
exports.getSocketDevAlertUrl = getSocketDevAlertUrl
|
|
3285
|
+
exports.getSocketDevPackageOverviewUrl = getSocketDevPackageOverviewUrl
|
|
3286
|
+
exports.getSocketDevPackageOverviewUrlFromPurl =
|
|
3287
|
+
getSocketDevPackageOverviewUrlFromPurl
|
|
3288
|
+
exports.getVisibleTokenPrefix = getVisibleTokenPrefix
|
|
3289
|
+
exports.globWorkspace = globWorkspace
|
|
3290
|
+
exports.handleApiCall = handleApiCall
|
|
3291
|
+
exports.handleApiCallNoSpinner = handleApiCallNoSpinner
|
|
3292
|
+
exports.handleUnsuccessfulApiResponse = handleUnsuccessfulApiResponse
|
|
3293
|
+
exports.hasDefaultToken = hasDefaultToken
|
|
3294
|
+
exports.idToPurl = idToPurl
|
|
3295
|
+
exports.isHelpFlag = isHelpFlag
|
|
3296
|
+
exports.isNpmBinPathShadowed = isNpmBinPathShadowed
|
|
3297
|
+
exports.isNpxBinPathShadowed = isNpxBinPathShadowed
|
|
3298
|
+
exports.isReadOnlyConfig = isReadOnlyConfig
|
|
3299
|
+
exports.isTestingV1 = isTestingV1
|
|
3300
|
+
exports.logAlertsMap = logAlertsMap
|
|
3301
|
+
exports.mapToObject = mapToObject
|
|
3302
|
+
exports.mdTable = mdTable
|
|
3303
|
+
exports.mdTableOfPairs = mdTableOfPairs
|
|
3304
|
+
exports.mdTableStringNumber = mdTableStringNumber
|
|
3305
|
+
exports.meowOrExit = meowOrExit
|
|
3306
|
+
exports.meowWithSubcommands = meowWithSubcommands
|
|
3307
|
+
exports.outputFlags = outputFlags
|
|
3308
|
+
exports.parsePnpmLockfileVersion = parsePnpmLockfileVersion
|
|
3309
|
+
exports.queryApiSafeJson = queryApiSafeJson
|
|
3310
|
+
exports.queryApiSafeText = queryApiSafeText
|
|
3311
|
+
exports.removeNodeModules = removeNodeModules
|
|
3312
|
+
exports.runAgentInstall = runAgentInstall
|
|
3313
|
+
exports.safeReadFile = safeReadFile
|
|
3314
|
+
exports.sensitiveConfigKeys = sensitiveConfigKeys
|
|
3315
|
+
exports.serializeResultJson = serializeResultJson
|
|
3316
|
+
exports.setupSdk = setupSdk
|
|
3317
|
+
exports.suggestOrgSlug = suggestOrgSlug
|
|
3318
|
+
exports.supportedConfigKeys = supportedConfigKeys
|
|
3319
|
+
exports.updateConfigValue = updateConfigValue
|
|
3320
|
+
exports.validationFlags = validationFlags
|
|
3321
|
+
exports.walkNestedMap = walkNestedMap
|
|
3322
|
+
//# debugId=ce901e44-4e3e-43e6-8016-50895b08fc53
|
|
3323
|
+
//# sourceMappingURL=utils.js.map
|