@socketsecurity/cli 0.5.3 → 0.6.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/lib/shadow/link.cjs
CHANGED
|
@@ -4,6 +4,11 @@ const path = require('path')
|
|
|
4
4
|
|
|
5
5
|
const which = require('which')
|
|
6
6
|
|
|
7
|
+
if (process.platform === 'win32') {
|
|
8
|
+
console.error('Socket npm and socket npx wrapper Windows suppport is limited to WSL at this time.')
|
|
9
|
+
process.exit(1)
|
|
10
|
+
}
|
|
11
|
+
|
|
7
12
|
/**
|
|
8
13
|
* @param {string} realDirname path to shadow/bin
|
|
9
14
|
* @param {'npm' | 'npx'} binname
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// THIS MUST BE CJS TO WORK WITH --require
|
|
2
1
|
/* eslint-disable no-console */
|
|
2
|
+
// THIS MUST BE CJS TO WORK WITH --require
|
|
3
3
|
'use strict'
|
|
4
4
|
|
|
5
5
|
const fs = require('fs')
|
|
@@ -7,15 +7,56 @@ const path = require('path')
|
|
|
7
7
|
const https = require('https')
|
|
8
8
|
const events = require('events')
|
|
9
9
|
const rl = require('readline')
|
|
10
|
+
const { PassThrough } = require('stream')
|
|
10
11
|
const oraPromise = import('ora')
|
|
11
12
|
const isInteractivePromise = import('is-interactive')
|
|
13
|
+
const chalkPromise = import('chalk')
|
|
12
14
|
const chalkMarkdownPromise = import('../utils/chalk-markdown.js')
|
|
15
|
+
const ipc_version = require('../../package.json').version
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
// due to update-notifier pkg being ESM only we actually spawn a subprocess sadly
|
|
19
|
+
require('child_process').spawnSync(process.execPath, [
|
|
20
|
+
path.join(__dirname, 'update-notifier.mjs')
|
|
21
|
+
], {
|
|
22
|
+
stdio: 'inherit'
|
|
23
|
+
})
|
|
24
|
+
} catch (e) {
|
|
25
|
+
// ignore if update notification fails
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @typedef {import('stream').Readable} Readable
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* @typedef {import('stream').Writable} Writable
|
|
33
|
+
*/
|
|
13
34
|
|
|
14
35
|
const pubToken = 'sktsec_t_--RAN5U4ivauy4w37-6aoKyYPDt5ZbaT5JBVMqiwKo_api'
|
|
15
36
|
|
|
16
37
|
// shadow `npm` and `npx` to mitigate subshells
|
|
17
38
|
require('./link.cjs')(fs.realpathSync(path.join(__dirname, 'bin')), 'npm')
|
|
18
39
|
|
|
40
|
+
/**
|
|
41
|
+
*
|
|
42
|
+
* @param {string} pkgid
|
|
43
|
+
* @returns {{name: string, version: string}}
|
|
44
|
+
*/
|
|
45
|
+
const pkgidParts = (pkgid) => {
|
|
46
|
+
const delimiter = pkgid.lastIndexOf('@')
|
|
47
|
+
const name = pkgid.slice(0, delimiter)
|
|
48
|
+
const version = pkgid.slice(delimiter + 1)
|
|
49
|
+
return { name, version }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @typedef PURLParts
|
|
54
|
+
* @property {'npm'} type
|
|
55
|
+
* @property {string} namespace_and_name
|
|
56
|
+
* @property {string} version
|
|
57
|
+
* @property {URL['href']} repository_url
|
|
58
|
+
*/
|
|
59
|
+
|
|
19
60
|
/**
|
|
20
61
|
* @param {string[]} pkgids
|
|
21
62
|
* @returns {AsyncGenerator<{eco: string, pkg: string, ver: string } & ({type: 'missing'} | {type: 'success', value: { issues: any[] }})>}
|
|
@@ -25,9 +66,7 @@ async function * batchScan (
|
|
|
25
66
|
) {
|
|
26
67
|
const query = {
|
|
27
68
|
packages: pkgids.map(pkgid => {
|
|
28
|
-
const
|
|
29
|
-
const name = pkgid.slice(0, delimiter)
|
|
30
|
-
const version = pkgid.slice(delimiter + 1)
|
|
69
|
+
const { name, version } = pkgidParts(pkgid)
|
|
31
70
|
return {
|
|
32
71
|
eco: 'npm', pkg: name, ver: version, top: true
|
|
33
72
|
}
|
|
@@ -65,6 +104,10 @@ let translations = null
|
|
|
65
104
|
*/
|
|
66
105
|
let formatter = null
|
|
67
106
|
|
|
107
|
+
const ttyServerPromise = chalkPromise.then(chalk => {
|
|
108
|
+
return createTTYServer(chalk.default.level)
|
|
109
|
+
})
|
|
110
|
+
|
|
68
111
|
const npmEntrypoint = fs.realpathSync(`${process.argv[1]}`)
|
|
69
112
|
/**
|
|
70
113
|
* @param {string} filepath
|
|
@@ -82,6 +125,8 @@ function findRoot (filepath) {
|
|
|
82
125
|
}
|
|
83
126
|
const npmDir = findRoot(path.dirname(npmEntrypoint))
|
|
84
127
|
const arboristLibClassPath = path.join(npmDir, 'node_modules', '@npmcli', 'arborist', 'lib', 'arborist', 'index.js')
|
|
128
|
+
const npmlog = require(path.join(npmDir, 'node_modules', 'npmlog', 'lib', 'log.js'))
|
|
129
|
+
|
|
85
130
|
/**
|
|
86
131
|
* @type {typeof import('@npmcli/arborist')}
|
|
87
132
|
*/
|
|
@@ -96,11 +141,11 @@ class SafeArborist extends Arborist {
|
|
|
96
141
|
constructor (...ctorArgs) {
|
|
97
142
|
const mutedArguments = [{
|
|
98
143
|
...(ctorArgs[0] ?? {}),
|
|
144
|
+
audit: true,
|
|
99
145
|
dryRun: true,
|
|
100
146
|
ignoreScripts: true,
|
|
101
147
|
save: false,
|
|
102
148
|
saveBundle: false,
|
|
103
|
-
audit: false,
|
|
104
149
|
// progress: false,
|
|
105
150
|
fund: false
|
|
106
151
|
}, ctorArgs.slice(1)]
|
|
@@ -140,39 +185,72 @@ class SafeArborist extends Arborist {
|
|
|
140
185
|
const diff = gatherDiff(this)
|
|
141
186
|
// @ts-expect-error types are wrong
|
|
142
187
|
args[0].dryRun = old.dryRun
|
|
143
|
-
// @ts-expect-error types are wrong
|
|
144
188
|
args[0].save = old.save
|
|
145
|
-
// @ts-expect-error types are wrong
|
|
146
189
|
args[0].saveBundle = old.saveBundle
|
|
147
|
-
// nothing to check, mmm already installed?
|
|
148
|
-
if (diff.
|
|
190
|
+
// nothing to check, mmm already installed or all private?
|
|
191
|
+
if (diff.findIndex(c => c.newPackage.repository_url === 'https://registry.npmjs.org') === -1) {
|
|
149
192
|
return this[kRiskyReify](...args)
|
|
150
193
|
}
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
194
|
+
const ttyServer = await ttyServerPromise
|
|
195
|
+
const proceed = await ttyServer.captureTTY(async (input, output, colorLevel) => {
|
|
196
|
+
if (input) {
|
|
197
|
+
const chalkNS = await chalkPromise
|
|
198
|
+
chalkNS.default.level = colorLevel
|
|
199
|
+
const oraNS = await oraPromise
|
|
200
|
+
const ora = () => {
|
|
201
|
+
return oraNS.default({
|
|
202
|
+
stream: output,
|
|
203
|
+
color: 'cyan',
|
|
204
|
+
isEnabled: true,
|
|
205
|
+
isSilent: false,
|
|
206
|
+
hideCursor: true,
|
|
207
|
+
discardStdin: true,
|
|
208
|
+
spinner: oraNS.spinners.dots,
|
|
164
209
|
})
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
210
|
+
}
|
|
211
|
+
const risky = await packagesHaveRiskyIssues(this.registry, diff, ora, input, output)
|
|
212
|
+
if (!risky) {
|
|
213
|
+
return true
|
|
214
|
+
}
|
|
215
|
+
const rl = require('readline')
|
|
216
|
+
const rlin = new PassThrough()
|
|
217
|
+
input.pipe(rlin, {
|
|
218
|
+
end: true
|
|
219
|
+
})
|
|
220
|
+
const rlout = new PassThrough()
|
|
221
|
+
rlout.pipe(output, {
|
|
222
|
+
end: false
|
|
223
|
+
})
|
|
224
|
+
const rli = rl.createInterface(rlin, rlout)
|
|
225
|
+
try {
|
|
226
|
+
while (true) {
|
|
227
|
+
/**
|
|
228
|
+
* @type {string}
|
|
229
|
+
*/
|
|
230
|
+
const answer = await new Promise((resolve) => {
|
|
231
|
+
rli.question('Accept risks of installing these packages (y/N)? ', (str) => resolve(str))
|
|
232
|
+
})
|
|
233
|
+
if (/^\s*y(es)?\s*$/i.test(answer)) {
|
|
234
|
+
return true
|
|
235
|
+
} else if (/^(\s*no?\s*|)$/i.test(answer)) {
|
|
236
|
+
return false
|
|
237
|
+
}
|
|
169
238
|
}
|
|
239
|
+
} finally {
|
|
240
|
+
rli.close()
|
|
241
|
+
}
|
|
242
|
+
} else {
|
|
243
|
+
if (await packagesHaveRiskyIssues(this.registry, diff, null, null, output)) {
|
|
244
|
+
throw new Error('Socket npm Unable to prompt to accept risk, need TTY to do so')
|
|
170
245
|
}
|
|
246
|
+
return true
|
|
171
247
|
}
|
|
248
|
+
return false
|
|
249
|
+
})
|
|
250
|
+
if (proceed) {
|
|
172
251
|
return this[kRiskyReify](...args)
|
|
173
252
|
} else {
|
|
174
|
-
|
|
175
|
-
throw new Error('Socket npm Unable to prompt to accept risk, need TTY to do so')
|
|
253
|
+
throw new Error('Socket npm exiting due to risks')
|
|
176
254
|
}
|
|
177
255
|
}
|
|
178
256
|
}
|
|
@@ -180,34 +258,18 @@ class SafeArborist extends Arborist {
|
|
|
180
258
|
require.cache[arboristLibClassPath].exports = SafeArborist
|
|
181
259
|
|
|
182
260
|
/**
|
|
183
|
-
* @
|
|
184
|
-
* @returns {{
|
|
261
|
+
* @typedef {{
|
|
185
262
|
* check: InstallEffect[],
|
|
186
263
|
* unknowns: InstallEffect[]
|
|
187
|
-
* }}
|
|
264
|
+
* }} InstallDiff
|
|
265
|
+
*/
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* @param {InstanceType<typeof Arborist>} arb
|
|
269
|
+
* @returns {InstallEffect[]}
|
|
188
270
|
*/
|
|
189
271
|
function gatherDiff (arb) {
|
|
190
|
-
|
|
191
|
-
const registry = arb.registry
|
|
192
|
-
/**
|
|
193
|
-
* @type {InstallEffect[]}
|
|
194
|
-
*/
|
|
195
|
-
const unknowns = []
|
|
196
|
-
/**
|
|
197
|
-
* @type {InstallEffect[]}
|
|
198
|
-
*/
|
|
199
|
-
const check = []
|
|
200
|
-
for (const node of walk(arb.diff)) {
|
|
201
|
-
if (node.resolved?.startsWith(registry)) {
|
|
202
|
-
check.push(node)
|
|
203
|
-
} else {
|
|
204
|
-
unknowns.push(node)
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
return {
|
|
208
|
-
check,
|
|
209
|
-
unknowns
|
|
210
|
-
}
|
|
272
|
+
return walk(arb.diff)
|
|
211
273
|
}
|
|
212
274
|
/**
|
|
213
275
|
* @typedef InstallEffect
|
|
@@ -216,6 +278,8 @@ function gatherDiff (arb) {
|
|
|
216
278
|
* @property {import('@npmcli/arborist').Node['pkgid']} pkgid
|
|
217
279
|
* @property {import('@npmcli/arborist').Node['resolved']} resolved
|
|
218
280
|
* @property {import('@npmcli/arborist').Node['location']} location
|
|
281
|
+
* @property {PURLParts | null} oldPackage
|
|
282
|
+
* @property {PURLParts} newPackage
|
|
219
283
|
*/
|
|
220
284
|
/**
|
|
221
285
|
* @param {import('@npmcli/arborist').Diff | null} diff
|
|
@@ -243,13 +307,36 @@ function walk (diff, needInfoOn = []) {
|
|
|
243
307
|
}
|
|
244
308
|
if (keep) {
|
|
245
309
|
if (diff.ideal?.pkgid) {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
310
|
+
/**
|
|
311
|
+
*
|
|
312
|
+
* @param {string} pkgid - `pkg@ver`
|
|
313
|
+
* @param {string} resolved - tarball link, should match `/name/-/name-ver.tgz` as tail, used to obtain repository_url
|
|
314
|
+
* @returns {PURLParts}
|
|
315
|
+
*/
|
|
316
|
+
function toPURL (pkgid, resolved) {
|
|
317
|
+
const repo = resolved
|
|
318
|
+
.replace(/#[\s\S]*$/u, '')
|
|
319
|
+
.replace(/\?[\s\S]*$/u, '')
|
|
320
|
+
.replace(/\/[^/]*\/-\/[\s\S]*$/u, '')
|
|
321
|
+
const { name, version } = pkgidParts(pkgid)
|
|
322
|
+
return {
|
|
323
|
+
type: 'npm',
|
|
324
|
+
namespace_and_name: name,
|
|
325
|
+
version,
|
|
326
|
+
repository_url: repo
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
if (diff.ideal.resolved && (!diff.actual || diff.actual.resolved)) {
|
|
330
|
+
needInfoOn.push({
|
|
331
|
+
existing,
|
|
332
|
+
action: diff.action,
|
|
333
|
+
location: diff.ideal.location,
|
|
334
|
+
pkgid: diff.ideal.pkgid,
|
|
335
|
+
newPackage: toPURL(diff.ideal.pkgid, diff.ideal.resolved),
|
|
336
|
+
oldPackage: diff.actual && diff.actual.resolved ? toPURL(diff.actual.pkgid, diff.actual.resolved) : null,
|
|
337
|
+
resolved: diff.ideal.resolved,
|
|
338
|
+
})
|
|
339
|
+
}
|
|
253
340
|
}
|
|
254
341
|
}
|
|
255
342
|
}
|
|
@@ -262,11 +349,14 @@ function walk (diff, needInfoOn = []) {
|
|
|
262
349
|
}
|
|
263
350
|
|
|
264
351
|
/**
|
|
352
|
+
* @param {string} registry
|
|
265
353
|
* @param {InstallEffect[]} pkgs
|
|
266
354
|
* @param {import('ora')['default'] | null} ora
|
|
355
|
+
* @param {Readable | null} input
|
|
356
|
+
* @param {Writable} ora
|
|
267
357
|
* @returns {Promise<boolean>}
|
|
268
358
|
*/
|
|
269
|
-
async function packagesHaveRiskyIssues (pkgs, ora = null) {
|
|
359
|
+
async function packagesHaveRiskyIssues (registry, pkgs, ora = null, input, output) {
|
|
270
360
|
let failed = false
|
|
271
361
|
if (pkgs.length) {
|
|
272
362
|
let remaining = pkgs.length
|
|
@@ -277,7 +367,7 @@ async function packagesHaveRiskyIssues (pkgs, ora = null) {
|
|
|
277
367
|
function getText () {
|
|
278
368
|
return `Looking up data for ${remaining} packages`
|
|
279
369
|
}
|
|
280
|
-
const spinner = ora ? ora(
|
|
370
|
+
const spinner = ora ? ora().start(getText()) : null
|
|
281
371
|
const pkgDatas = []
|
|
282
372
|
try {
|
|
283
373
|
for await (const pkgData of batchScan(pkgs.map(pkg => pkg.pkgid))) {
|
|
@@ -326,7 +416,7 @@ async function packagesHaveRiskyIssues (pkgs, ora = null) {
|
|
|
326
416
|
formatter ??= new ((await chalkMarkdownPromise).ChalkOrMarkdown)(false)
|
|
327
417
|
const name = pkgData.pkg
|
|
328
418
|
const version = pkgData.ver
|
|
329
|
-
|
|
419
|
+
output.write(`(socket) ${formatter.hyperlink(`${name}@${version}`, `https://socket.dev/npm/package/${name}/overview/${version}`)} contains risks:\n`)
|
|
330
420
|
if (translations) {
|
|
331
421
|
for (const failure of failures) {
|
|
332
422
|
const type = failure.type
|
|
@@ -335,8 +425,8 @@ async function packagesHaveRiskyIssues (pkgs, ora = null) {
|
|
|
335
425
|
const issueTypeTranslation = translations.issues[type]
|
|
336
426
|
// TODO: emoji seems to misalign terminals sometimes
|
|
337
427
|
// @ts-ignore
|
|
338
|
-
const msg = ` ${issueTypeTranslation.title} - ${issueTypeTranslation.description}`
|
|
339
|
-
|
|
428
|
+
const msg = ` ${issueTypeTranslation.title} - ${issueTypeTranslation.description}\n`
|
|
429
|
+
output.write(msg)
|
|
340
430
|
}
|
|
341
431
|
}
|
|
342
432
|
}
|
|
@@ -349,8 +439,6 @@ async function packagesHaveRiskyIssues (pkgs, ora = null) {
|
|
|
349
439
|
if (spinner) {
|
|
350
440
|
spinner.text = getText()
|
|
351
441
|
}
|
|
352
|
-
} else {
|
|
353
|
-
spinner?.stop()
|
|
354
442
|
}
|
|
355
443
|
pkgDatas.push(pkgData)
|
|
356
444
|
}
|
|
@@ -367,3 +455,195 @@ async function packagesHaveRiskyIssues (pkgs, ora = null) {
|
|
|
367
455
|
return false
|
|
368
456
|
}
|
|
369
457
|
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* @param {import('chalk')['default']['level']} colorLevel
|
|
461
|
+
* @returns {Promise<{ captureTTY<RET extends any>(mutexFn: (input: Readable | null, output: Writable, colorLevel: import('chalk')['default']['level']) => Promise<RET>): Promise<RET> }>}
|
|
462
|
+
*/
|
|
463
|
+
async function createTTYServer (colorLevel) {
|
|
464
|
+
const TTY_IPC = process.env.SOCKET_SECURITY_TTY_IPC
|
|
465
|
+
const net = require('net')
|
|
466
|
+
/**
|
|
467
|
+
* @type {import('readline')}
|
|
468
|
+
*/
|
|
469
|
+
let readline
|
|
470
|
+
const isSTDINInteractive = (await isInteractivePromise).default({
|
|
471
|
+
stream: process.stdin
|
|
472
|
+
})
|
|
473
|
+
if (!isSTDINInteractive && TTY_IPC) {
|
|
474
|
+
return {
|
|
475
|
+
async captureTTY (mutexFn) {
|
|
476
|
+
return new Promise((resolve, reject) => {
|
|
477
|
+
const conn = net.createConnection({
|
|
478
|
+
path: TTY_IPC
|
|
479
|
+
}).on('error', reject)
|
|
480
|
+
let captured = false
|
|
481
|
+
const bufs = []
|
|
482
|
+
conn.on('data', function awaitCapture (chunk) {
|
|
483
|
+
bufs.push(chunk)
|
|
484
|
+
const lineBuff = Buffer.concat(bufs)
|
|
485
|
+
try {
|
|
486
|
+
if (!captured) {
|
|
487
|
+
const EOL = lineBuff.indexOf('\n'.charCodeAt(0))
|
|
488
|
+
if (EOL !== -1) {
|
|
489
|
+
conn.removeListener('data', awaitCapture)
|
|
490
|
+
conn.push(lineBuff.slice(EOL + 1))
|
|
491
|
+
lineBuff = null
|
|
492
|
+
captured = true
|
|
493
|
+
const {
|
|
494
|
+
ipc_version: remote_ipc_version,
|
|
495
|
+
capabilities: { input: hasInput, output: hasOutput, colorLevel: ipcColorLevel }
|
|
496
|
+
} = JSON.parse(lineBuff.slice(0, EOL).toString('utf-8'))
|
|
497
|
+
if (remote_ipc_version !== ipc_version) {
|
|
498
|
+
throw new Error('Mismatched STDIO tunnel IPC version, ensure you only have 1 version of socket CLI being called.')
|
|
499
|
+
}
|
|
500
|
+
const input = hasInput ? new PassThrough() : null
|
|
501
|
+
input.pause()
|
|
502
|
+
conn.pipe(input)
|
|
503
|
+
const output = hasOutput ? new PassThrough() : null
|
|
504
|
+
output.pipe(conn)
|
|
505
|
+
// make ora happy
|
|
506
|
+
// @ts-ignore
|
|
507
|
+
output.isTTY = true
|
|
508
|
+
// @ts-ignore
|
|
509
|
+
output.cursorTo = function cursorTo (x, y, callback) {
|
|
510
|
+
readline = readline || require('readline')
|
|
511
|
+
readline.cursorTo(this, x, y, callback)
|
|
512
|
+
}
|
|
513
|
+
// @ts-ignore
|
|
514
|
+
output.clearLine = function clearLine (dir, callback) {
|
|
515
|
+
readline = readline || require('readline')
|
|
516
|
+
readline.clearLine(this, dir, callback)
|
|
517
|
+
}
|
|
518
|
+
mutexFn(hasInput ? input : null, hasOutput ? output : null, ipcColorLevel)
|
|
519
|
+
.then(resolve, reject)
|
|
520
|
+
.finally(() => {
|
|
521
|
+
conn.unref()
|
|
522
|
+
conn.end()
|
|
523
|
+
input.end()
|
|
524
|
+
output.end()
|
|
525
|
+
// process.exit(13)
|
|
526
|
+
})
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
} catch (e) {
|
|
530
|
+
reject(e)
|
|
531
|
+
}
|
|
532
|
+
})
|
|
533
|
+
})
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
const pendingCaptures = []
|
|
538
|
+
let captured = false
|
|
539
|
+
const sock = path.join(require('os').tmpdir(), `socket-security-tty-${process.pid}.sock`)
|
|
540
|
+
process.env.SOCKET_SECURITY_TTY_IPC = sock
|
|
541
|
+
try {
|
|
542
|
+
await require('fs/promises').unlink(sock)
|
|
543
|
+
} catch (e) {
|
|
544
|
+
if (e.code !== 'ENOENT') {
|
|
545
|
+
throw e
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
process.on('exit', () => {
|
|
549
|
+
ttyServer.close()
|
|
550
|
+
try {
|
|
551
|
+
require('fs').unlinkSync(sock)
|
|
552
|
+
} catch (e) {
|
|
553
|
+
if (e.code !== 'ENOENT') {
|
|
554
|
+
throw e
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
})
|
|
558
|
+
const input = isSTDINInteractive ? process.stdin : null
|
|
559
|
+
const output = process.stderr
|
|
560
|
+
const ttyServer = await new Promise((resolve, reject) => {
|
|
561
|
+
const server = net.createServer(async (conn) => {
|
|
562
|
+
if (captured) {
|
|
563
|
+
const captured = new Promise((resolve) => {
|
|
564
|
+
pendingCaptures.push({
|
|
565
|
+
resolve
|
|
566
|
+
})
|
|
567
|
+
})
|
|
568
|
+
await captured
|
|
569
|
+
} else {
|
|
570
|
+
captured = true
|
|
571
|
+
}
|
|
572
|
+
const wasProgressEnabled = npmlog.progressEnabled
|
|
573
|
+
npmlog.pause()
|
|
574
|
+
if (wasProgressEnabled) {
|
|
575
|
+
npmlog.disableProgress()
|
|
576
|
+
}
|
|
577
|
+
conn.write(`${JSON.stringify({
|
|
578
|
+
ipc_version,
|
|
579
|
+
capabilities: {
|
|
580
|
+
input: Boolean(input),
|
|
581
|
+
output: true,
|
|
582
|
+
colorLevel
|
|
583
|
+
}
|
|
584
|
+
})}\n`)
|
|
585
|
+
conn.on('data', (data) => {
|
|
586
|
+
output.write(data)
|
|
587
|
+
})
|
|
588
|
+
conn.on('error', (e) => {
|
|
589
|
+
output.write(`there was an error prompting from a subshell (${e.message}), socket npm closing`)
|
|
590
|
+
process.exit(1)
|
|
591
|
+
})
|
|
592
|
+
input.on('data', (data) => {
|
|
593
|
+
conn.write(data)
|
|
594
|
+
})
|
|
595
|
+
input.on('end', () => {
|
|
596
|
+
conn.unref()
|
|
597
|
+
conn.end()
|
|
598
|
+
if (wasProgressEnabled) {
|
|
599
|
+
npmlog.enableProgress()
|
|
600
|
+
}
|
|
601
|
+
npmlog.resume()
|
|
602
|
+
nextCapture()
|
|
603
|
+
})
|
|
604
|
+
}).listen(sock, (err) => {
|
|
605
|
+
if (err) reject(err)
|
|
606
|
+
else resolve(server)
|
|
607
|
+
}).unref()
|
|
608
|
+
})
|
|
609
|
+
/**
|
|
610
|
+
*
|
|
611
|
+
*/
|
|
612
|
+
function nextCapture () {
|
|
613
|
+
if (pendingCaptures.length > 0) {
|
|
614
|
+
const nextCapture = pendingCaptures.shift()
|
|
615
|
+
nextCapture.resolve()
|
|
616
|
+
} else {
|
|
617
|
+
captured = false
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
return {
|
|
621
|
+
async captureTTY (mutexFn) {
|
|
622
|
+
if (captured) {
|
|
623
|
+
const captured = new Promise((resolve) => {
|
|
624
|
+
pendingCaptures.push({
|
|
625
|
+
resolve
|
|
626
|
+
})
|
|
627
|
+
})
|
|
628
|
+
await captured
|
|
629
|
+
} else {
|
|
630
|
+
captured = true
|
|
631
|
+
}
|
|
632
|
+
const wasProgressEnabled = npmlog.progressEnabled
|
|
633
|
+
try {
|
|
634
|
+
npmlog.pause()
|
|
635
|
+
if (wasProgressEnabled) {
|
|
636
|
+
npmlog.disableProgress()
|
|
637
|
+
}
|
|
638
|
+
// need await here for proper finally timing
|
|
639
|
+
return await mutexFn(input, output, colorLevel)
|
|
640
|
+
} finally {
|
|
641
|
+
if (wasProgressEnabled) {
|
|
642
|
+
npmlog.enableProgress()
|
|
643
|
+
}
|
|
644
|
+
npmlog.resume()
|
|
645
|
+
nextCapture()
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@socketsecurity/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "CLI tool for Socket.dev",
|
|
5
5
|
"homepage": "http://github.com/SocketDev/socket-cli-js",
|
|
6
6
|
"repository": {
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"@tsconfig/node14": "^1.0.3",
|
|
48
48
|
"@types/chai": "^4.3.3",
|
|
49
49
|
"@types/chai-as-promised": "^7.1.5",
|
|
50
|
-
"@types/mocha": "^10.0.
|
|
50
|
+
"@types/mocha": "^10.0.1",
|
|
51
51
|
"@types/mock-fs": "^4.13.1",
|
|
52
52
|
"@types/node": "^14.18.31",
|
|
53
53
|
"@types/npm": "^7.19.0",
|
package/lib/shadow/global.d.ts
DELETED