@socketsecurity/cli 0.10.1 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -22
- package/bin/npm +2 -0
- package/bin/npx +2 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +3419 -0
- package/dist/errors.d.ts +7 -0
- package/dist/link.d.ts +2 -0
- package/dist/link.js +45 -0
- package/dist/npm-cli.d.ts +2 -0
- package/dist/npm-cli.js +84 -0
- package/dist/npm-injection.d.ts +1 -0
- package/dist/npm-injection.js +913 -0
- package/dist/npm-injection2.d.ts +25 -0
- package/dist/npm-injection2.js +899 -0
- package/dist/npx-cli.d.ts +2 -0
- package/dist/npx-cli.js +60 -0
- package/dist/path-resolve.d.ts +12 -0
- package/dist/path-resolve.js +139 -0
- package/dist/sdk.d.ts +27 -0
- package/dist/sdk.js +224 -0
- package/dist/settings.d.ts +9 -0
- package/dist/type-helpers.d.ts +3 -0
- package/dist/vendor.js +25421 -0
- package/package.json +105 -52
- package/{lib/shadow/translations.json → translations.json} +20 -20
- package/cli.js +0 -72
- package/lib/commands/audit-log/index.js +0 -162
- package/lib/commands/cdxgen/index.js +0 -211
- package/lib/commands/dependencies/index.js +0 -150
- package/lib/commands/index.js +0 -14
- package/lib/commands/info/index.js +0 -287
- package/lib/commands/login/index.js +0 -170
- package/lib/commands/logout/index.js +0 -35
- package/lib/commands/npm/index.js +0 -27
- package/lib/commands/npx/index.js +0 -22
- package/lib/commands/raw-npm/index.js +0 -59
- package/lib/commands/raw-npx/index.js +0 -59
- package/lib/commands/report/create.js +0 -251
- package/lib/commands/report/index.js +0 -24
- package/lib/commands/report/view.js +0 -176
- package/lib/commands/repos/create.js +0 -166
- package/lib/commands/repos/delete.js +0 -93
- package/lib/commands/repos/index.js +0 -30
- package/lib/commands/repos/list.js +0 -170
- package/lib/commands/repos/update.js +0 -166
- package/lib/commands/repos/view.js +0 -128
- package/lib/commands/scan/create.js +0 -245
- package/lib/commands/scan/delete.js +0 -112
- package/lib/commands/scan/index.js +0 -30
- package/lib/commands/scan/list.js +0 -192
- package/lib/commands/scan/metadata.js +0 -113
- package/lib/commands/scan/stream.js +0 -115
- package/lib/commands/wrapper/index.js +0 -199
- package/lib/flags/command.js +0 -14
- package/lib/flags/index.js +0 -3
- package/lib/flags/output.js +0 -16
- package/lib/flags/validation.js +0 -14
- package/lib/shadow/bin/npm +0 -2
- package/lib/shadow/bin/npx +0 -2
- package/lib/shadow/link.cjs +0 -50
- package/lib/shadow/npm-cli.cjs +0 -27
- package/lib/shadow/npm-injection.cjs +0 -649
- package/lib/shadow/npx-cli.cjs +0 -27
- package/lib/shadow/package.json +0 -3
- package/lib/shadow/tty-server.cjs +0 -222
- package/lib/shadow/update-notifier.mjs +0 -3
- package/lib/utils/api-helpers.js +0 -42
- package/lib/utils/chalk-markdown.js +0 -125
- package/lib/utils/errors.js +0 -14
- package/lib/utils/flags.js +0 -27
- package/lib/utils/format-issues.js +0 -99
- package/lib/utils/formatting.js +0 -47
- package/lib/utils/issue-rules.cjs +0 -180
- package/lib/utils/meow-with-subcommands.js +0 -87
- package/lib/utils/misc.js +0 -61
- package/lib/utils/path-resolve.js +0 -204
- package/lib/utils/sdk.js +0 -99
- package/lib/utils/settings.js +0 -69
- package/lib/utils/type-helpers.cjs +0 -13
- package/lib/utils/update-notifier.js +0 -18
|
@@ -1,649 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-console */
|
|
2
|
-
// THIS MUST BE CJS TO WORK WITH --require
|
|
3
|
-
'use strict'
|
|
4
|
-
|
|
5
|
-
const events = require('events')
|
|
6
|
-
const fs = require('fs')
|
|
7
|
-
const https = require('https')
|
|
8
|
-
const path = require('path')
|
|
9
|
-
const rl = require('readline')
|
|
10
|
-
const { PassThrough } = require('stream')
|
|
11
|
-
|
|
12
|
-
const config = require('@socketsecurity/config')
|
|
13
|
-
|
|
14
|
-
const oraPromise = import('ora')
|
|
15
|
-
const isInteractivePromise = import('is-interactive')
|
|
16
|
-
const chalkPromise = import('chalk')
|
|
17
|
-
const chalkMarkdownPromise = import('../utils/chalk-markdown.js')
|
|
18
|
-
const settingsPromise = import('../utils/settings.js')
|
|
19
|
-
const sdkPromise = import('../utils/sdk.js')
|
|
20
|
-
const createTTYServer = require('./tty-server.cjs')
|
|
21
|
-
const { createIssueUXLookup } = require('../utils/issue-rules.cjs')
|
|
22
|
-
const { isErrnoException } = require('../utils/type-helpers.cjs')
|
|
23
|
-
|
|
24
|
-
try {
|
|
25
|
-
// due to update-notifier pkg being ESM only we actually spawn a subprocess sadly
|
|
26
|
-
require('child_process').spawnSync(process.execPath, [
|
|
27
|
-
path.join(__dirname, 'update-notifier.mjs')
|
|
28
|
-
], {
|
|
29
|
-
stdio: 'inherit'
|
|
30
|
-
})
|
|
31
|
-
} catch (e) {
|
|
32
|
-
// ignore if update notification fails
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* @typedef {import('stream').Readable} Readable
|
|
37
|
-
*/
|
|
38
|
-
/**
|
|
39
|
-
* @typedef {import('stream').Writable} Writable
|
|
40
|
-
*/
|
|
41
|
-
|
|
42
|
-
const pubTokenPromise = sdkPromise.then(({ getDefaultKey, FREE_API_KEY }) => getDefaultKey() || FREE_API_KEY)
|
|
43
|
-
const apiKeySettingsInit = sdkPromise.then(async ({ setupSdk }) => {
|
|
44
|
-
try {
|
|
45
|
-
const sdk = await setupSdk(await pubTokenPromise)
|
|
46
|
-
const orgResult = await sdk.getOrganizations()
|
|
47
|
-
if (!orgResult.success) {
|
|
48
|
-
throw new Error('Failed to fetch Socket organization info: ' + orgResult.error.message)
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* @type {(Exclude<typeof orgResult.data.organizations[string], undefined>)[]}
|
|
52
|
-
*/
|
|
53
|
-
const orgs = []
|
|
54
|
-
for (const org of Object.values(orgResult.data.organizations)) {
|
|
55
|
-
if (org) {
|
|
56
|
-
orgs.push(org)
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
const result = await sdk.postSettings(orgs.map(org => {
|
|
60
|
-
return {
|
|
61
|
-
organization: org.id
|
|
62
|
-
}
|
|
63
|
-
}))
|
|
64
|
-
if (!result.success) {
|
|
65
|
-
throw new Error('Failed to fetch API key settings: ' + result.error.message)
|
|
66
|
-
}
|
|
67
|
-
return {
|
|
68
|
-
orgs,
|
|
69
|
-
settings: result.data
|
|
70
|
-
}
|
|
71
|
-
} catch (e) {
|
|
72
|
-
if (e && typeof e === 'object' && 'cause' in e) {
|
|
73
|
-
const cause = e.cause
|
|
74
|
-
if (isErrnoException(cause)) {
|
|
75
|
-
if (cause.code === 'ENOTFOUND' || cause.code === 'ECONNREFUSED') {
|
|
76
|
-
throw new Error('Unable to connect to socket.dev, ensure internet connectivity before retrying', {
|
|
77
|
-
cause: e
|
|
78
|
-
})
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
throw e
|
|
83
|
-
}
|
|
84
|
-
})
|
|
85
|
-
// mark apiKeySettingsInit as handled
|
|
86
|
-
apiKeySettingsInit.catch(() => {})
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
*
|
|
90
|
-
*/
|
|
91
|
-
async function findSocketYML () {
|
|
92
|
-
let prevDir = null
|
|
93
|
-
let dir = process.cwd()
|
|
94
|
-
const fs = require('fs/promises')
|
|
95
|
-
while (dir !== prevDir) {
|
|
96
|
-
const ymlPath = path.join(dir, 'socket.yml')
|
|
97
|
-
const yml = fs.readFile(ymlPath, 'utf-8')
|
|
98
|
-
// mark as handled
|
|
99
|
-
yml.catch(() => {})
|
|
100
|
-
const yamlPath = path.join(dir, 'socket.yaml')
|
|
101
|
-
const yaml = fs.readFile(yamlPath, 'utf-8')
|
|
102
|
-
// mark as handled
|
|
103
|
-
yaml.catch(() => {})
|
|
104
|
-
/**
|
|
105
|
-
* @param {unknown} e
|
|
106
|
-
* @returns {boolean}
|
|
107
|
-
*/
|
|
108
|
-
function checkFileFoundError (e) {
|
|
109
|
-
if (isErrnoException(e)) {
|
|
110
|
-
if (e.code !== 'ENOENT' && e.code !== 'EISDIR') {
|
|
111
|
-
throw e
|
|
112
|
-
}
|
|
113
|
-
return false
|
|
114
|
-
}
|
|
115
|
-
return true
|
|
116
|
-
}
|
|
117
|
-
try {
|
|
118
|
-
return {
|
|
119
|
-
path: ymlPath,
|
|
120
|
-
parsed: config.parseSocketConfig(await yml)
|
|
121
|
-
}
|
|
122
|
-
} catch (e) {
|
|
123
|
-
if (checkFileFoundError(e)) {
|
|
124
|
-
throw new Error('Found file but was unable to parse ' + ymlPath)
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
try {
|
|
128
|
-
return {
|
|
129
|
-
path: ymlPath,
|
|
130
|
-
parsed: config.parseSocketConfig(await yaml)
|
|
131
|
-
}
|
|
132
|
-
} catch (e) {
|
|
133
|
-
if (checkFileFoundError(e)) {
|
|
134
|
-
throw new Error('Found file but was unable to parse ' + yamlPath)
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
prevDir = dir
|
|
138
|
-
dir = path.join(dir, '..')
|
|
139
|
-
}
|
|
140
|
-
return null
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* @type {Promise<ReturnType<import('../utils/issue-rules.cjs')['createIssueUXLookup']> | undefined>}
|
|
145
|
-
*/
|
|
146
|
-
const uxLookupInit = settingsPromise.then(async ({ getSetting }) => {
|
|
147
|
-
const enforcedOrgs = getSetting('enforcedOrgs') ?? []
|
|
148
|
-
const remoteSettings = await apiKeySettingsInit
|
|
149
|
-
const { orgs, settings } = remoteSettings
|
|
150
|
-
|
|
151
|
-
// remove any organizations not being enforced
|
|
152
|
-
for (const [i, org] of orgs.entries()) {
|
|
153
|
-
if (!enforcedOrgs.includes(org.id)) {
|
|
154
|
-
settings.entries.splice(i, 1)
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const socketYml = await findSocketYML()
|
|
159
|
-
if (socketYml) {
|
|
160
|
-
settings.entries.push({
|
|
161
|
-
start: socketYml.path,
|
|
162
|
-
// @ts-ignore
|
|
163
|
-
settings: {
|
|
164
|
-
[socketYml.path]: {
|
|
165
|
-
deferTo: null,
|
|
166
|
-
issueRules: socketYml.parsed.issueRules
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
})
|
|
170
|
-
}
|
|
171
|
-
return createIssueUXLookup(settings)
|
|
172
|
-
})
|
|
173
|
-
// mark uxLookupInit as handled
|
|
174
|
-
uxLookupInit.catch(() => {})
|
|
175
|
-
|
|
176
|
-
// shadow `npm` and `npx` to mitigate subshells
|
|
177
|
-
require('./link.cjs')(fs.realpathSync(path.join(__dirname, 'bin')), 'npm')
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
*
|
|
181
|
-
* @param {string} pkgid
|
|
182
|
-
* @returns {{name: string, version: string}}
|
|
183
|
-
*/
|
|
184
|
-
const pkgidParts = (pkgid) => {
|
|
185
|
-
const delimiter = pkgid.lastIndexOf('@')
|
|
186
|
-
const name = pkgid.slice(0, delimiter)
|
|
187
|
-
const version = pkgid.slice(delimiter + 1)
|
|
188
|
-
return { name, version }
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* @typedef PURLParts
|
|
193
|
-
* @property {'npm'} type
|
|
194
|
-
* @property {string} namespace_and_name
|
|
195
|
-
* @property {string} version
|
|
196
|
-
* @property {URL['href']} repository_url
|
|
197
|
-
*/
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* @param {string[]} pkgids
|
|
201
|
-
* @returns {AsyncGenerator<{eco: string, pkg: string, ver: string } & ({type: 'missing'} | {type: 'success', value: { issues: any[] }})>}
|
|
202
|
-
*/
|
|
203
|
-
async function * batchScan (
|
|
204
|
-
pkgids
|
|
205
|
-
) {
|
|
206
|
-
const pubToken = await pubTokenPromise
|
|
207
|
-
const query = {
|
|
208
|
-
packages: pkgids.map(pkgid => {
|
|
209
|
-
const { name, version } = pkgidParts(pkgid)
|
|
210
|
-
return {
|
|
211
|
-
eco: 'npm', pkg: name, ver: version, top: true
|
|
212
|
-
}
|
|
213
|
-
})
|
|
214
|
-
}
|
|
215
|
-
// TODO: migrate to SDK
|
|
216
|
-
const pkgDataReq = https.request(
|
|
217
|
-
'https://api.socket.dev/v0/scan/batch',
|
|
218
|
-
{
|
|
219
|
-
method: 'POST',
|
|
220
|
-
headers: {
|
|
221
|
-
Authorization: `Basic ${Buffer.from(`${pubToken}:`).toString('base64url')}`
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
).end(
|
|
225
|
-
JSON.stringify(query)
|
|
226
|
-
)
|
|
227
|
-
const [res] = await events.once(pkgDataReq, 'response')
|
|
228
|
-
const isSuccess = res.statusCode === 200
|
|
229
|
-
if (!isSuccess) {
|
|
230
|
-
throw new Error('Socket API Error: ' + res.statusCode)
|
|
231
|
-
}
|
|
232
|
-
const rli = rl.createInterface(res)
|
|
233
|
-
for await (const line of rli) {
|
|
234
|
-
const result = JSON.parse(line)
|
|
235
|
-
yield result
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* @type {import('./translations.json') | null}
|
|
241
|
-
*/
|
|
242
|
-
let translations = null
|
|
243
|
-
/**
|
|
244
|
-
* @type {import('../utils/chalk-markdown.js').ChalkOrMarkdown | null}
|
|
245
|
-
*/
|
|
246
|
-
let formatter = null
|
|
247
|
-
|
|
248
|
-
const ttyServerPromise = chalkPromise.then(async (chalk) => {
|
|
249
|
-
return createTTYServer(chalk.default.level, (await isInteractivePromise).default({
|
|
250
|
-
stream: process.stdin
|
|
251
|
-
}), npmlog)
|
|
252
|
-
})
|
|
253
|
-
|
|
254
|
-
const npmEntrypoint = fs.realpathSync(`${process.argv[1]}`)
|
|
255
|
-
/**
|
|
256
|
-
* @param {string} filepath
|
|
257
|
-
* @returns {string | null}
|
|
258
|
-
*/
|
|
259
|
-
function findRoot (filepath) {
|
|
260
|
-
if (path.basename(filepath) === 'npm') {
|
|
261
|
-
return filepath
|
|
262
|
-
}
|
|
263
|
-
const parent = path.dirname(filepath)
|
|
264
|
-
if (parent === filepath) {
|
|
265
|
-
return null
|
|
266
|
-
}
|
|
267
|
-
return findRoot(parent)
|
|
268
|
-
}
|
|
269
|
-
const npmDir = findRoot(path.dirname(npmEntrypoint))
|
|
270
|
-
if (npmDir === null) {
|
|
271
|
-
console.error('Unable to find npm cli install directory, this is potentiall a bug with socket-npm caused by changes to npm cli.')
|
|
272
|
-
console.error(`Searched parent directories of ${npmEntrypoint}`)
|
|
273
|
-
process.exit(127)
|
|
274
|
-
}
|
|
275
|
-
let arboristLibClassPath
|
|
276
|
-
try {
|
|
277
|
-
arboristLibClassPath = path.join(npmDir, 'node_modules', '@npmcli', 'arborist', 'lib', 'arborist', 'index.js')
|
|
278
|
-
} catch (e) {
|
|
279
|
-
console.error('Unable to integrate with npm cli internals, this is potentially a bug with socket-npm caused by changes to npm cli.')
|
|
280
|
-
process.exit(127);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
let npmlog
|
|
284
|
-
|
|
285
|
-
try {
|
|
286
|
-
npmlog = require(path.join(npmDir, 'node_modules', 'npmlog', 'lib', 'log.js'))
|
|
287
|
-
} catch {
|
|
288
|
-
try {
|
|
289
|
-
const { log } = require(path.join(npmDir, 'node_modules', 'proc-log', 'lib', 'index.js'))
|
|
290
|
-
npmlog = log
|
|
291
|
-
} catch {
|
|
292
|
-
console.error('Unable to integrate with npm cli logging infrastructure, this is potentially a bug with socket-npm caused by changes to npm cli.')
|
|
293
|
-
process.exit(127);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* @type {import('pacote')}
|
|
299
|
-
*/
|
|
300
|
-
const pacote = require(path.join(npmDir, 'node_modules', 'pacote'))
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* @type {typeof import('@npmcli/arborist')}
|
|
304
|
-
*/
|
|
305
|
-
const Arborist = require(arboristLibClassPath)
|
|
306
|
-
|
|
307
|
-
const kCtorArgs = Symbol('ctorArgs')
|
|
308
|
-
const kRiskyReify = Symbol('riskyReify')
|
|
309
|
-
class SafeArborist extends Arborist {
|
|
310
|
-
/**
|
|
311
|
-
* @param {ConstructorParameters<typeof Arborist>} ctorArgs
|
|
312
|
-
*/
|
|
313
|
-
constructor (...ctorArgs) {
|
|
314
|
-
const mutedArguments = [{
|
|
315
|
-
...(ctorArgs[0] ?? {}),
|
|
316
|
-
audit: true,
|
|
317
|
-
dryRun: true,
|
|
318
|
-
ignoreScripts: true,
|
|
319
|
-
save: false,
|
|
320
|
-
saveBundle: false,
|
|
321
|
-
// progress: false,
|
|
322
|
-
fund: false
|
|
323
|
-
}, ctorArgs.slice(1)]
|
|
324
|
-
super(...mutedArguments)
|
|
325
|
-
this[kCtorArgs] = ctorArgs
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
/**
|
|
329
|
-
* @param {Parameters<InstanceType<typeof Arborist>['reify']>} args
|
|
330
|
-
*/
|
|
331
|
-
async [kRiskyReify] (...args) {
|
|
332
|
-
// safe arborist has suffered side effects and must be rebuilt from scratch
|
|
333
|
-
const arb = new Arborist(...this[kCtorArgs])
|
|
334
|
-
const ret = await arb.reify(...args)
|
|
335
|
-
Object.assign(this, arb)
|
|
336
|
-
return ret
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
/**
|
|
340
|
-
* @param {Parameters<InstanceType<typeof Arborist>['reify']>} args
|
|
341
|
-
* @override
|
|
342
|
-
*/
|
|
343
|
-
async reify (...args) {
|
|
344
|
-
// @ts-expect-error types are wrong
|
|
345
|
-
if (args[0]?.dryRun) {
|
|
346
|
-
return this[kRiskyReify](...args)
|
|
347
|
-
}
|
|
348
|
-
args[0] ??= {}
|
|
349
|
-
const old = {
|
|
350
|
-
dryRun: false,
|
|
351
|
-
save: Boolean(args[0].save ?? true),
|
|
352
|
-
saveBundle: Boolean(args[0].saveBundle ?? false),
|
|
353
|
-
...args[0]
|
|
354
|
-
}
|
|
355
|
-
// @ts-expect-error types are wrong
|
|
356
|
-
args[0].dryRun = true
|
|
357
|
-
args[0].save = false
|
|
358
|
-
args[0].saveBundle = false
|
|
359
|
-
// const originalDescriptors = Object.getOwnPropertyDescriptors(this)
|
|
360
|
-
// TODO: make this deal w/ any refactor to private fields by punching the class itself
|
|
361
|
-
await super.reify(...args)
|
|
362
|
-
const diff = gatherDiff(this)
|
|
363
|
-
// @ts-expect-error types are wrong
|
|
364
|
-
args[0].dryRun = old.dryRun
|
|
365
|
-
args[0].save = old.save
|
|
366
|
-
args[0].saveBundle = old.saveBundle
|
|
367
|
-
// nothing to check, mmm already installed or all private?
|
|
368
|
-
if (diff.findIndex(c => c.newPackage.repository_url === 'https://registry.npmjs.org') === -1) {
|
|
369
|
-
return this[kRiskyReify](...args)
|
|
370
|
-
}
|
|
371
|
-
const ttyServer = await ttyServerPromise
|
|
372
|
-
const proceed = await ttyServer.captureTTY(async (input, output, colorLevel) => {
|
|
373
|
-
if (input && output) {
|
|
374
|
-
const chalkNS = await chalkPromise
|
|
375
|
-
chalkNS.default.level = colorLevel
|
|
376
|
-
const oraNS = await oraPromise
|
|
377
|
-
const ora = () => {
|
|
378
|
-
return oraNS.default({
|
|
379
|
-
stream: output,
|
|
380
|
-
color: 'cyan',
|
|
381
|
-
isEnabled: true,
|
|
382
|
-
isSilent: false,
|
|
383
|
-
hideCursor: true,
|
|
384
|
-
discardStdin: true,
|
|
385
|
-
spinner: oraNS.spinners.dots,
|
|
386
|
-
})
|
|
387
|
-
}
|
|
388
|
-
const risky = await packagesHaveRiskyIssues(this, this.registry, diff, ora, input, output)
|
|
389
|
-
if (!risky) {
|
|
390
|
-
return true
|
|
391
|
-
}
|
|
392
|
-
const rl = require('readline')
|
|
393
|
-
const rlin = new PassThrough()
|
|
394
|
-
input.pipe(rlin, {
|
|
395
|
-
end: true
|
|
396
|
-
})
|
|
397
|
-
const rlout = new PassThrough()
|
|
398
|
-
rlout.pipe(output, {
|
|
399
|
-
end: false
|
|
400
|
-
})
|
|
401
|
-
const rli = rl.createInterface(rlin, rlout)
|
|
402
|
-
try {
|
|
403
|
-
while (true) {
|
|
404
|
-
/**
|
|
405
|
-
* @type {string}
|
|
406
|
-
*/
|
|
407
|
-
const answer = await new Promise((resolve) => {
|
|
408
|
-
rli.question('Accept risks of installing these packages (y/N)? ', (str) => resolve(str))
|
|
409
|
-
})
|
|
410
|
-
if (/^\s*y(es)?\s*$/i.test(answer)) {
|
|
411
|
-
return true
|
|
412
|
-
} else if (/^(\s*no?\s*|)$/i.test(answer)) {
|
|
413
|
-
return false
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
} finally {
|
|
417
|
-
rli.close()
|
|
418
|
-
}
|
|
419
|
-
} else {
|
|
420
|
-
if (await packagesHaveRiskyIssues(this, this.registry, diff, null, null, output)) {
|
|
421
|
-
throw new Error('Socket npm Unable to prompt to accept risk, need TTY to do so')
|
|
422
|
-
}
|
|
423
|
-
return true
|
|
424
|
-
}
|
|
425
|
-
// @ts-ignore paranoia
|
|
426
|
-
// eslint-disable-next-line
|
|
427
|
-
return false
|
|
428
|
-
})
|
|
429
|
-
if (proceed) {
|
|
430
|
-
return this[kRiskyReify](...args)
|
|
431
|
-
} else {
|
|
432
|
-
throw new Error('Socket npm exiting due to risks')
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
// @ts-ignore
|
|
437
|
-
require.cache[arboristLibClassPath].exports = SafeArborist
|
|
438
|
-
|
|
439
|
-
/**
|
|
440
|
-
* @typedef {{
|
|
441
|
-
* check: InstallEffect[],
|
|
442
|
-
* unknowns: InstallEffect[]
|
|
443
|
-
* }} InstallDiff
|
|
444
|
-
*/
|
|
445
|
-
|
|
446
|
-
/**
|
|
447
|
-
* @param {InstanceType<typeof Arborist>} arb
|
|
448
|
-
* @returns {InstallEffect[]}
|
|
449
|
-
*/
|
|
450
|
-
function gatherDiff (arb) {
|
|
451
|
-
return walk(arb.diff)
|
|
452
|
-
}
|
|
453
|
-
/**
|
|
454
|
-
* @typedef InstallEffect
|
|
455
|
-
* @property {import('@npmcli/arborist').Diff['action']} action
|
|
456
|
-
* @property {import('@npmcli/arborist').Node['pkgid'] | null} existing
|
|
457
|
-
* @property {import('@npmcli/arborist').Node['pkgid']} pkgid
|
|
458
|
-
* @property {import('@npmcli/arborist').Node['resolved']} resolved
|
|
459
|
-
* @property {import('@npmcli/arborist').Node['location']} location
|
|
460
|
-
* @property {PURLParts | null} oldPackage
|
|
461
|
-
* @property {PURLParts} newPackage
|
|
462
|
-
*/
|
|
463
|
-
/**
|
|
464
|
-
* @param {import('@npmcli/arborist').Diff | null} diff
|
|
465
|
-
* @param {InstallEffect[]} needInfoOn
|
|
466
|
-
* @returns {InstallEffect[]}
|
|
467
|
-
*/
|
|
468
|
-
function walk (diff, needInfoOn = []) {
|
|
469
|
-
if (!diff) {
|
|
470
|
-
return needInfoOn
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
if (diff.action) {
|
|
474
|
-
const sameVersion = diff.actual?.package.version === diff.ideal?.package.version
|
|
475
|
-
let keep = false
|
|
476
|
-
let existing = null
|
|
477
|
-
if (diff.action === 'CHANGE') {
|
|
478
|
-
if (!sameVersion) {
|
|
479
|
-
existing = diff.actual.pkgid
|
|
480
|
-
keep = true
|
|
481
|
-
} else {
|
|
482
|
-
// console.log('SKIPPING META CHANGE ON', diff)
|
|
483
|
-
}
|
|
484
|
-
} else {
|
|
485
|
-
keep = diff.action !== 'REMOVE'
|
|
486
|
-
}
|
|
487
|
-
if (keep) {
|
|
488
|
-
if (diff.ideal?.pkgid) {
|
|
489
|
-
/**
|
|
490
|
-
*
|
|
491
|
-
* @param {string} pkgid - `pkg@ver`
|
|
492
|
-
* @param {string} resolved - tarball link, should match `/name/-/name-ver.tgz` as tail, used to obtain repository_url
|
|
493
|
-
* @returns {PURLParts}
|
|
494
|
-
*/
|
|
495
|
-
function toPURL (pkgid, resolved) {
|
|
496
|
-
const repo = resolved
|
|
497
|
-
.replace(/#[\s\S]*$/u, '')
|
|
498
|
-
.replace(/\?[\s\S]*$/u, '')
|
|
499
|
-
.replace(/\/[^/]*\/-\/[\s\S]*$/u, '')
|
|
500
|
-
const { name, version } = pkgidParts(pkgid)
|
|
501
|
-
return {
|
|
502
|
-
type: 'npm',
|
|
503
|
-
namespace_and_name: name,
|
|
504
|
-
version,
|
|
505
|
-
repository_url: repo
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
if (diff.ideal.resolved && (!diff.actual || diff.actual.resolved)) {
|
|
509
|
-
needInfoOn.push({
|
|
510
|
-
existing,
|
|
511
|
-
action: diff.action,
|
|
512
|
-
location: diff.ideal.location,
|
|
513
|
-
pkgid: diff.ideal.pkgid,
|
|
514
|
-
newPackage: toPURL(diff.ideal.pkgid, diff.ideal.resolved),
|
|
515
|
-
oldPackage: diff.actual && diff.actual.resolved ? toPURL(diff.actual.pkgid, diff.actual.resolved) : null,
|
|
516
|
-
resolved: diff.ideal.resolved,
|
|
517
|
-
})
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
if (diff.children) {
|
|
523
|
-
for (const child of diff.children) {
|
|
524
|
-
walk(child, needInfoOn)
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
return needInfoOn
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
/**
|
|
531
|
-
* @param {SafeArborist} safeArb
|
|
532
|
-
* @param {string} _registry
|
|
533
|
-
* @param {InstallEffect[]} pkgs
|
|
534
|
-
* @param {import('ora')['default'] | null} ora
|
|
535
|
-
* @param {Readable | null} [_input]
|
|
536
|
-
* @param {Writable | null} [output]
|
|
537
|
-
* @returns {Promise<boolean>}
|
|
538
|
-
*/
|
|
539
|
-
async function packagesHaveRiskyIssues (safeArb, _registry, pkgs, ora = null, _input, output) {
|
|
540
|
-
let failed = false
|
|
541
|
-
if (pkgs.length) {
|
|
542
|
-
let remaining = pkgs.length
|
|
543
|
-
/**
|
|
544
|
-
*
|
|
545
|
-
* @returns {string}
|
|
546
|
-
*/
|
|
547
|
-
function getText () {
|
|
548
|
-
return `Looking up data for ${remaining} packages`
|
|
549
|
-
}
|
|
550
|
-
const spinner = ora ? ora().start(getText()) : null
|
|
551
|
-
const pkgDatas = []
|
|
552
|
-
try {
|
|
553
|
-
// TODO: determine org based on cwd, pass in
|
|
554
|
-
const uxLookup = await uxLookupInit
|
|
555
|
-
|
|
556
|
-
for await (const pkgData of batchScan(pkgs.map(pkg => pkg.pkgid))) {
|
|
557
|
-
/**
|
|
558
|
-
* @type {Array<any>}
|
|
559
|
-
*/
|
|
560
|
-
let failures = []
|
|
561
|
-
let displayWarning = false
|
|
562
|
-
const name = pkgData.pkg
|
|
563
|
-
const version = pkgData.ver
|
|
564
|
-
let blocked = false
|
|
565
|
-
if (pkgData.type === 'missing') {
|
|
566
|
-
failed = true
|
|
567
|
-
failures.push({
|
|
568
|
-
type: 'missingDependency'
|
|
569
|
-
})
|
|
570
|
-
continue
|
|
571
|
-
} else {
|
|
572
|
-
for (const failure of pkgData.value.issues) {
|
|
573
|
-
const ux = await uxLookup({ package: { name, version }, issue: { type: failure.type } })
|
|
574
|
-
if (ux.display || ux.block) {
|
|
575
|
-
failures.push({ raw: failure, block: ux.block })
|
|
576
|
-
// before we ask about problematic issues, check to see if they already existed in the old version
|
|
577
|
-
// if they did, be quiet
|
|
578
|
-
const pkg = pkgs.find(pkg => pkg.pkgid === `${pkgData.pkg}@${pkgData.ver}` && pkg.existing?.startsWith(pkgData.pkg + '@'))
|
|
579
|
-
if (pkg?.existing) {
|
|
580
|
-
for await (const oldPkgData of batchScan([pkg.existing])) {
|
|
581
|
-
if (oldPkgData.type === 'success') {
|
|
582
|
-
failures = failures.filter(
|
|
583
|
-
issue => oldPkgData.value.issues.find(oldIssue => oldIssue.type === issue.raw.type) == null
|
|
584
|
-
)
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
if (ux.block) {
|
|
590
|
-
failed = true
|
|
591
|
-
blocked = true
|
|
592
|
-
}
|
|
593
|
-
if (ux.display) {
|
|
594
|
-
displayWarning = true
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
if (!blocked) {
|
|
599
|
-
const pkg = pkgs.find(pkg => pkg.pkgid === `${pkgData.pkg}@${pkgData.ver}`)
|
|
600
|
-
if (pkg) {
|
|
601
|
-
pacote.tarball.stream(pkg.pkgid, (stream) => {
|
|
602
|
-
stream.resume()
|
|
603
|
-
// @ts-ignore pacote does a naughty
|
|
604
|
-
return stream.promise()
|
|
605
|
-
}, { ...safeArb[kCtorArgs][0] })
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
if (displayWarning) {
|
|
609
|
-
translations ??= JSON.parse(fs.readFileSync(path.join(__dirname, '/translations.json'), 'utf-8'))
|
|
610
|
-
formatter ??= new ((await chalkMarkdownPromise).ChalkOrMarkdown)(false)
|
|
611
|
-
spinner?.stop()
|
|
612
|
-
output?.write(`(socket) ${formatter.hyperlink(`${name}@${version}`, `https://socket.dev/npm/package/${name}/overview/${version}`)} contains risks:\n`)
|
|
613
|
-
const lines = new Set()
|
|
614
|
-
for (const failure of failures.sort((a, b) => a.raw.type < b.raw.type ? -1 : 1)) {
|
|
615
|
-
const type = failure.raw.type
|
|
616
|
-
if (type) {
|
|
617
|
-
// @ts-ignore
|
|
618
|
-
const issueTypeTranslation = translations.issues[type]
|
|
619
|
-
// TODO: emoji seems to misalign terminals sometimes
|
|
620
|
-
// @ts-ignore
|
|
621
|
-
lines.add(` ${issueTypeTranslation?.title ?? type}${failure.block ? '' : ' (non-blocking)'} - ${issueTypeTranslation?.description ?? ''}\n`)
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
for (const line of lines) {
|
|
625
|
-
output?.write(line)
|
|
626
|
-
}
|
|
627
|
-
spinner?.start()
|
|
628
|
-
}
|
|
629
|
-
remaining--
|
|
630
|
-
if (remaining !== 0) {
|
|
631
|
-
if (spinner) {
|
|
632
|
-
spinner.text = getText()
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
pkgDatas.push(pkgData)
|
|
636
|
-
}
|
|
637
|
-
return failed
|
|
638
|
-
} finally {
|
|
639
|
-
if (spinner?.isSpinning) {
|
|
640
|
-
spinner?.stop()
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
} else {
|
|
644
|
-
if (ora) {
|
|
645
|
-
ora('').succeed('No changes detected')
|
|
646
|
-
}
|
|
647
|
-
return false
|
|
648
|
-
}
|
|
649
|
-
}
|
package/lib/shadow/npx-cli.cjs
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// THIS FILE USES .cjs to get around the extension-free entrypoint problem with ESM
|
|
3
|
-
'use strict'
|
|
4
|
-
const { spawn } = require('child_process')
|
|
5
|
-
const { realpathSync } = require('fs')
|
|
6
|
-
const path = require('path')
|
|
7
|
-
|
|
8
|
-
const realFilename = realpathSync(__filename)
|
|
9
|
-
const realDirname = path.dirname(realFilename)
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
*/
|
|
13
|
-
async function main () {
|
|
14
|
-
const npxpath = await require('./link.cjs')(path.join(realDirname, 'bin'), 'npx')
|
|
15
|
-
process.exitCode = 1
|
|
16
|
-
const injectionpath = path.join(realDirname, 'npm-injection.cjs')
|
|
17
|
-
spawn(process.execPath, ['--require', injectionpath, npxpath, ...process.argv.slice(2)], {
|
|
18
|
-
stdio: 'inherit'
|
|
19
|
-
}).on('exit', (code, signal) => {
|
|
20
|
-
if (signal) {
|
|
21
|
-
process.kill(process.pid, signal)
|
|
22
|
-
} else if (code !== null) {
|
|
23
|
-
process.exit(code)
|
|
24
|
-
}
|
|
25
|
-
})
|
|
26
|
-
}
|
|
27
|
-
main()
|
package/lib/shadow/package.json
DELETED