@socketsecurity/cli 0.7.1 → 0.8.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/README.md +3 -2
- package/lib/commands/info/index.js +3 -3
- package/lib/commands/login/index.js +87 -17
- package/lib/commands/logout/index.js +1 -0
- package/lib/commands/report/create.js +18 -18
- package/lib/commands/report/view.js +20 -5
- package/lib/flags/output.js +2 -2
- package/lib/shadow/link.cjs +12 -12
- package/lib/shadow/npm-injection.cjs +206 -255
- package/lib/shadow/tty-server.cjs +221 -0
- package/lib/utils/api-helpers.js +1 -3
- package/lib/utils/issue-rules.cjs +180 -0
- package/lib/utils/misc.js +1 -1
- package/lib/utils/path-resolve.js +37 -5
- package/lib/utils/sdk.js +19 -7
- package/lib/utils/settings.js +17 -5
- package/lib/utils/{type-helpers.js → type-helpers.cjs} +1 -1
- package/lib/utils/update-notifier.js +3 -0
- package/package.json +11 -14
|
@@ -2,18 +2,24 @@
|
|
|
2
2
|
// THIS MUST BE CJS TO WORK WITH --require
|
|
3
3
|
'use strict'
|
|
4
4
|
|
|
5
|
+
const events = require('events')
|
|
5
6
|
const fs = require('fs')
|
|
6
|
-
const path = require('path')
|
|
7
7
|
const https = require('https')
|
|
8
|
-
const
|
|
8
|
+
const path = require('path')
|
|
9
9
|
const rl = require('readline')
|
|
10
10
|
const { PassThrough } = require('stream')
|
|
11
|
+
|
|
12
|
+
const config = require('@socketsecurity/config')
|
|
13
|
+
|
|
11
14
|
const oraPromise = import('ora')
|
|
12
15
|
const isInteractivePromise = import('is-interactive')
|
|
13
16
|
const chalkPromise = import('chalk')
|
|
14
17
|
const chalkMarkdownPromise = import('../utils/chalk-markdown.js')
|
|
15
18
|
const settingsPromise = import('../utils/settings.js')
|
|
16
|
-
const
|
|
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')
|
|
17
23
|
|
|
18
24
|
try {
|
|
19
25
|
// due to update-notifier pkg being ESM only we actually spawn a subprocess sadly
|
|
@@ -33,9 +39,119 @@ try {
|
|
|
33
39
|
* @typedef {import('stream').Writable} Writable
|
|
34
40
|
*/
|
|
35
41
|
|
|
36
|
-
const pubTokenPromise =
|
|
37
|
-
|
|
38
|
-
)
|
|
42
|
+
const pubTokenPromise = sdkPromise.then(({ getDefaultKey, FREE_API_KEY }) => getDefaultKey() || FREE_API_KEY)
|
|
43
|
+
const apiKeySettingsPromise = sdkPromise.then(async ({ setupSdk }) => {
|
|
44
|
+
const sdk = await setupSdk(await pubTokenPromise)
|
|
45
|
+
const orgResult = await sdk.getOrganizations()
|
|
46
|
+
if (!orgResult.success) {
|
|
47
|
+
throw new Error('Failed to fetch Socket organization info: ' + orgResult.error.message)
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* @type {(Exclude<typeof orgResult.data.organizations[string], undefined>)[]}
|
|
51
|
+
*/
|
|
52
|
+
const orgs = []
|
|
53
|
+
for (const org of Object.values(orgResult.data.organizations)) {
|
|
54
|
+
if (org) {
|
|
55
|
+
orgs.push(org)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const result = await sdk.postSettings(orgs.map(org => {
|
|
59
|
+
return {
|
|
60
|
+
organization: org.id
|
|
61
|
+
}
|
|
62
|
+
}))
|
|
63
|
+
if (!result.success) {
|
|
64
|
+
throw new Error('Failed to fetch API key settings: ' + result.error.message)
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
orgs,
|
|
68
|
+
settings: result.data
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
*
|
|
74
|
+
*/
|
|
75
|
+
async function findSocketYML () {
|
|
76
|
+
let prevDir = null
|
|
77
|
+
let dir = process.cwd()
|
|
78
|
+
const fs = require('fs/promises')
|
|
79
|
+
while (dir !== prevDir) {
|
|
80
|
+
const ymlPath = path.join(dir, 'socket.yml')
|
|
81
|
+
// mark as handled
|
|
82
|
+
const yml = fs.readFile(ymlPath, 'utf-8').catch(() => {})
|
|
83
|
+
const yamlPath = path.join(dir, 'socket.yaml')
|
|
84
|
+
// mark as handled
|
|
85
|
+
const yaml = fs.readFile(yamlPath, 'utf-8').catch(() => {})
|
|
86
|
+
try {
|
|
87
|
+
const txt = await yml
|
|
88
|
+
if (txt != null) {
|
|
89
|
+
return {
|
|
90
|
+
path: ymlPath,
|
|
91
|
+
parsed: config.parseSocketConfig(txt)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
} catch (e) {
|
|
95
|
+
if (isErrnoException(e)) {
|
|
96
|
+
if (e.code !== 'ENOENT' && e.code !== 'EISDIR') {
|
|
97
|
+
throw e
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
throw new Error('Found file but was unable to parse ' + ymlPath)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
const txt = await yaml
|
|
105
|
+
if (txt != null) {
|
|
106
|
+
return {
|
|
107
|
+
path: yamlPath,
|
|
108
|
+
parsed: config.parseSocketConfig(txt)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
} catch (e) {
|
|
112
|
+
if (isErrnoException(e)) {
|
|
113
|
+
if (e.code !== 'ENOENT' && e.code !== 'EISDIR') {
|
|
114
|
+
throw e
|
|
115
|
+
}
|
|
116
|
+
} else {
|
|
117
|
+
throw new Error('Found file but was unable to parse ' + yamlPath)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
prevDir = dir
|
|
121
|
+
dir = path.join(dir, '..')
|
|
122
|
+
}
|
|
123
|
+
return null
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* @type {Promise<ReturnType<import('../utils/issue-rules.cjs')['createIssueUXLookup']>>}
|
|
128
|
+
*/
|
|
129
|
+
const uxLookupPromise = settingsPromise.then(async ({ getSetting }) => {
|
|
130
|
+
const enforcedOrgs = getSetting('enforcedOrgs') ?? []
|
|
131
|
+
const { orgs, settings } = await apiKeySettingsPromise
|
|
132
|
+
|
|
133
|
+
// remove any organizations not being enforced
|
|
134
|
+
for (const [i, org] of orgs.entries()) {
|
|
135
|
+
if (!enforcedOrgs.includes(org.id)) {
|
|
136
|
+
settings.entries.splice(i, 1)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const socketYml = await findSocketYML()
|
|
141
|
+
if (socketYml) {
|
|
142
|
+
settings.entries.push({
|
|
143
|
+
start: socketYml.path,
|
|
144
|
+
// @ts-ignore
|
|
145
|
+
settings: {
|
|
146
|
+
[socketYml.path]: {
|
|
147
|
+
deferTo: null,
|
|
148
|
+
issueRules: socketYml.parsed.issueRules
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
return createIssueUXLookup(settings)
|
|
154
|
+
})
|
|
39
155
|
|
|
40
156
|
// shadow `npm` and `npx` to mitigate subshells
|
|
41
157
|
require('./link.cjs')(fs.realpathSync(path.join(__dirname, 'bin')), 'npm')
|
|
@@ -76,6 +192,7 @@ async function * batchScan (
|
|
|
76
192
|
}
|
|
77
193
|
})
|
|
78
194
|
}
|
|
195
|
+
// TODO: migrate to SDK
|
|
79
196
|
const pkgDataReq = https.request(
|
|
80
197
|
'https://api.socket.dev/v0/scan/batch',
|
|
81
198
|
{
|
|
@@ -108,8 +225,10 @@ let translations = null
|
|
|
108
225
|
*/
|
|
109
226
|
let formatter = null
|
|
110
227
|
|
|
111
|
-
const ttyServerPromise = chalkPromise.then(chalk => {
|
|
112
|
-
return createTTYServer(chalk.default.level)
|
|
228
|
+
const ttyServerPromise = chalkPromise.then(async (chalk) => {
|
|
229
|
+
return createTTYServer(chalk.default.level, (await isInteractivePromise).default({
|
|
230
|
+
stream: process.stdin
|
|
231
|
+
}), npmlog)
|
|
113
232
|
})
|
|
114
233
|
|
|
115
234
|
const npmEntrypoint = fs.realpathSync(`${process.argv[1]}`)
|
|
@@ -130,6 +249,10 @@ function findRoot (filepath) {
|
|
|
130
249
|
const npmDir = findRoot(path.dirname(npmEntrypoint))
|
|
131
250
|
const arboristLibClassPath = path.join(npmDir, 'node_modules', '@npmcli', 'arborist', 'lib', 'arborist', 'index.js')
|
|
132
251
|
const npmlog = require(path.join(npmDir, 'node_modules', 'npmlog', 'lib', 'log.js'))
|
|
252
|
+
/**
|
|
253
|
+
* @type {import('pacote')}
|
|
254
|
+
*/
|
|
255
|
+
const pacote = require(path.join(npmDir, 'node_modules', 'pacote'))
|
|
133
256
|
|
|
134
257
|
/**
|
|
135
258
|
* @type {typeof import('@npmcli/arborist')}
|
|
@@ -178,7 +301,12 @@ class SafeArborist extends Arborist {
|
|
|
178
301
|
return this[kRiskyReify](...args)
|
|
179
302
|
}
|
|
180
303
|
args[0] ??= {}
|
|
181
|
-
const old = {
|
|
304
|
+
const old = {
|
|
305
|
+
dryRun: false,
|
|
306
|
+
save: Boolean(args[0].save ?? true),
|
|
307
|
+
saveBundle: Boolean(args[0].saveBundle ?? false),
|
|
308
|
+
...args[0]
|
|
309
|
+
}
|
|
182
310
|
// @ts-expect-error types are wrong
|
|
183
311
|
args[0].dryRun = true
|
|
184
312
|
args[0].save = false
|
|
@@ -197,7 +325,7 @@ class SafeArborist extends Arborist {
|
|
|
197
325
|
}
|
|
198
326
|
const ttyServer = await ttyServerPromise
|
|
199
327
|
const proceed = await ttyServer.captureTTY(async (input, output, colorLevel) => {
|
|
200
|
-
if (input) {
|
|
328
|
+
if (input && output) {
|
|
201
329
|
const chalkNS = await chalkPromise
|
|
202
330
|
chalkNS.default.level = colorLevel
|
|
203
331
|
const oraNS = await oraPromise
|
|
@@ -212,7 +340,7 @@ class SafeArborist extends Arborist {
|
|
|
212
340
|
spinner: oraNS.spinners.dots,
|
|
213
341
|
})
|
|
214
342
|
}
|
|
215
|
-
const risky = await packagesHaveRiskyIssues(this.registry, diff, ora, input, output)
|
|
343
|
+
const risky = await packagesHaveRiskyIssues(this, this.registry, diff, ora, input, output)
|
|
216
344
|
if (!risky) {
|
|
217
345
|
return true
|
|
218
346
|
}
|
|
@@ -244,11 +372,13 @@ class SafeArborist extends Arborist {
|
|
|
244
372
|
rli.close()
|
|
245
373
|
}
|
|
246
374
|
} else {
|
|
247
|
-
if (await packagesHaveRiskyIssues(this.registry, diff, null, null, output)) {
|
|
375
|
+
if (await packagesHaveRiskyIssues(this, this.registry, diff, null, null, output)) {
|
|
248
376
|
throw new Error('Socket npm Unable to prompt to accept risk, need TTY to do so')
|
|
249
377
|
}
|
|
250
378
|
return true
|
|
251
379
|
}
|
|
380
|
+
// @ts-ignore paranoia
|
|
381
|
+
// eslint-disable-next-line
|
|
252
382
|
return false
|
|
253
383
|
})
|
|
254
384
|
if (proceed) {
|
|
@@ -353,14 +483,15 @@ function walk (diff, needInfoOn = []) {
|
|
|
353
483
|
}
|
|
354
484
|
|
|
355
485
|
/**
|
|
356
|
-
* @param {
|
|
486
|
+
* @param {SafeArborist} safeArb
|
|
487
|
+
* @param {string} _registry
|
|
357
488
|
* @param {InstallEffect[]} pkgs
|
|
358
489
|
* @param {import('ora')['default'] | null} ora
|
|
359
|
-
* @param {Readable | null}
|
|
360
|
-
* @param {Writable}
|
|
490
|
+
* @param {Readable | null} [_input]
|
|
491
|
+
* @param {Writable | null} [output]
|
|
361
492
|
* @returns {Promise<boolean>}
|
|
362
493
|
*/
|
|
363
|
-
async function packagesHaveRiskyIssues (
|
|
494
|
+
async function packagesHaveRiskyIssues (safeArb, _registry, pkgs, ora = null, _input, output) {
|
|
364
495
|
let failed = false
|
|
365
496
|
if (pkgs.length) {
|
|
366
497
|
let remaining = pkgs.length
|
|
@@ -374,69 +505,81 @@ async function packagesHaveRiskyIssues (registry, pkgs, ora = null, input, outpu
|
|
|
374
505
|
const spinner = ora ? ora().start(getText()) : null
|
|
375
506
|
const pkgDatas = []
|
|
376
507
|
try {
|
|
508
|
+
// TODO: determine org based on cwd, pass in
|
|
509
|
+
const uxLookup = await uxLookupPromise
|
|
510
|
+
|
|
377
511
|
for await (const pkgData of batchScan(pkgs.map(pkg => pkg.pkgid))) {
|
|
512
|
+
/**
|
|
513
|
+
* @type {Array<any>}
|
|
514
|
+
*/
|
|
378
515
|
let failures = []
|
|
516
|
+
let displayWarning = false
|
|
517
|
+
const name = pkgData.pkg
|
|
518
|
+
const version = pkgData.ver
|
|
519
|
+
let blocked = false
|
|
379
520
|
if (pkgData.type === 'missing') {
|
|
521
|
+
failed = true
|
|
380
522
|
failures.push({
|
|
381
523
|
type: 'missingDependency'
|
|
382
524
|
})
|
|
383
525
|
continue
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
// before we ask about problematic issues, check to see if they already existed in the old version
|
|
403
|
-
// if they did, be quiet
|
|
404
|
-
if (failures.length) {
|
|
405
|
-
const pkg = pkgs.find(pkg => pkg.pkgid === `${pkgData.pkg}@${pkgData.ver}` && pkg.existing?.startsWith(pkgData.pkg))
|
|
406
|
-
if (pkg?.existing) {
|
|
407
|
-
for await (const oldPkgData of batchScan([pkg.existing])) {
|
|
408
|
-
if (oldPkgData.type === 'success') {
|
|
409
|
-
failures = failures.filter(
|
|
410
|
-
issue => oldPkgData.value.issues.find(oldIssue => oldIssue.type === issue.type) == null
|
|
411
|
-
)
|
|
526
|
+
} else {
|
|
527
|
+
for (const failure of pkgData.value.issues) {
|
|
528
|
+
const ux = await uxLookup({ package: { name, version }, issue: { type: failure.type } })
|
|
529
|
+
if (ux.display || ux.block) {
|
|
530
|
+
failures.push({ raw: failure, block: ux.block })
|
|
531
|
+
// before we ask about problematic issues, check to see if they already existed in the old version
|
|
532
|
+
// if they did, be quiet
|
|
533
|
+
const pkg = pkgs.find(pkg => pkg.pkgid === `${pkgData.pkg}@${pkgData.ver}` && pkg.existing?.startsWith(pkgData.pkg + '@'))
|
|
534
|
+
if (pkg?.existing) {
|
|
535
|
+
for await (const oldPkgData of batchScan([pkg.existing])) {
|
|
536
|
+
if (oldPkgData.type === 'success') {
|
|
537
|
+
failures = failures.filter(
|
|
538
|
+
issue => oldPkgData.value.issues.find(oldIssue => oldIssue.type === issue.raw.type) == null
|
|
539
|
+
)
|
|
540
|
+
}
|
|
541
|
+
}
|
|
412
542
|
}
|
|
413
543
|
}
|
|
544
|
+
if (ux.block) {
|
|
545
|
+
failed = true
|
|
546
|
+
blocked = true
|
|
547
|
+
}
|
|
548
|
+
if (ux.display) {
|
|
549
|
+
displayWarning = true
|
|
550
|
+
}
|
|
414
551
|
}
|
|
415
552
|
}
|
|
416
|
-
if (
|
|
417
|
-
|
|
418
|
-
|
|
553
|
+
if (!blocked) {
|
|
554
|
+
const pkg = pkgs.find(pkg => pkg.pkgid === `${pkgData.pkg}@${pkgData.ver}`)
|
|
555
|
+
if (pkg) {
|
|
556
|
+
pacote.tarball.stream(pkg.pkgid, (stream) => {
|
|
557
|
+
stream.resume()
|
|
558
|
+
// @ts-ignore pacote does a naughty
|
|
559
|
+
return stream.promise()
|
|
560
|
+
}, { ...safeArb[kCtorArgs][0] })
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
if (displayWarning) {
|
|
419
564
|
translations ??= JSON.parse(fs.readFileSync(path.join(__dirname, '/translations.json'), 'utf-8'))
|
|
420
565
|
formatter ??= new ((await chalkMarkdownPromise).ChalkOrMarkdown)(false)
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
const msg = ` ${issueTypeTranslation.title} - ${issueTypeTranslation.description}\n`
|
|
433
|
-
output.write(msg)
|
|
434
|
-
}
|
|
566
|
+
spinner?.stop()
|
|
567
|
+
output?.write(`(socket) ${formatter.hyperlink(`${name}@${version}`, `https://socket.dev/npm/package/${name}/overview/${version}`)} contains risks:\n`)
|
|
568
|
+
const lines = new Set()
|
|
569
|
+
for (const failure of failures.sort((a, b) => a.raw.type < b.raw.type ? -1 : 1)) {
|
|
570
|
+
const type = failure.raw.type
|
|
571
|
+
if (type) {
|
|
572
|
+
// @ts-ignore
|
|
573
|
+
const issueTypeTranslation = translations.issues[type]
|
|
574
|
+
// TODO: emoji seems to misalign terminals sometimes
|
|
575
|
+
// @ts-ignore
|
|
576
|
+
lines.add(` ${issueTypeTranslation?.title ?? type}${failure.block ? '' : ' (non-blocking)'} - ${issueTypeTranslation?.description ?? ''}\n`)
|
|
435
577
|
}
|
|
436
578
|
}
|
|
579
|
+
for (const line of lines) {
|
|
580
|
+
output?.write(line)
|
|
581
|
+
}
|
|
437
582
|
spinner?.start()
|
|
438
|
-
} else {
|
|
439
|
-
// TODO: have pacote/cacache download non-problematic files while waiting
|
|
440
583
|
}
|
|
441
584
|
remaining--
|
|
442
585
|
if (remaining !== 0) {
|
|
@@ -459,195 +602,3 @@ async function packagesHaveRiskyIssues (registry, pkgs, ora = null, input, outpu
|
|
|
459
602
|
return false
|
|
460
603
|
}
|
|
461
604
|
}
|
|
462
|
-
|
|
463
|
-
/**
|
|
464
|
-
* @param {import('chalk')['default']['level']} colorLevel
|
|
465
|
-
* @returns {Promise<{ captureTTY<RET extends any>(mutexFn: (input: Readable | null, output: Writable, colorLevel: import('chalk')['default']['level']) => Promise<RET>): Promise<RET> }>}
|
|
466
|
-
*/
|
|
467
|
-
async function createTTYServer (colorLevel) {
|
|
468
|
-
const TTY_IPC = process.env.SOCKET_SECURITY_TTY_IPC
|
|
469
|
-
const net = require('net')
|
|
470
|
-
/**
|
|
471
|
-
* @type {import('readline')}
|
|
472
|
-
*/
|
|
473
|
-
let readline
|
|
474
|
-
const isSTDINInteractive = (await isInteractivePromise).default({
|
|
475
|
-
stream: process.stdin
|
|
476
|
-
})
|
|
477
|
-
if (!isSTDINInteractive && TTY_IPC) {
|
|
478
|
-
return {
|
|
479
|
-
async captureTTY (mutexFn) {
|
|
480
|
-
return new Promise((resolve, reject) => {
|
|
481
|
-
const conn = net.createConnection({
|
|
482
|
-
path: TTY_IPC
|
|
483
|
-
}).on('error', reject)
|
|
484
|
-
let captured = false
|
|
485
|
-
const bufs = []
|
|
486
|
-
conn.on('data', function awaitCapture (chunk) {
|
|
487
|
-
bufs.push(chunk)
|
|
488
|
-
const lineBuff = Buffer.concat(bufs)
|
|
489
|
-
try {
|
|
490
|
-
if (!captured) {
|
|
491
|
-
const EOL = lineBuff.indexOf('\n'.charCodeAt(0))
|
|
492
|
-
if (EOL !== -1) {
|
|
493
|
-
conn.removeListener('data', awaitCapture)
|
|
494
|
-
conn.push(lineBuff.slice(EOL + 1))
|
|
495
|
-
lineBuff = null
|
|
496
|
-
captured = true
|
|
497
|
-
const {
|
|
498
|
-
ipc_version: remote_ipc_version,
|
|
499
|
-
capabilities: { input: hasInput, output: hasOutput, colorLevel: ipcColorLevel }
|
|
500
|
-
} = JSON.parse(lineBuff.slice(0, EOL).toString('utf-8'))
|
|
501
|
-
if (remote_ipc_version !== ipc_version) {
|
|
502
|
-
throw new Error('Mismatched STDIO tunnel IPC version, ensure you only have 1 version of socket CLI being called.')
|
|
503
|
-
}
|
|
504
|
-
const input = hasInput ? new PassThrough() : null
|
|
505
|
-
input.pause()
|
|
506
|
-
conn.pipe(input)
|
|
507
|
-
const output = hasOutput ? new PassThrough() : null
|
|
508
|
-
output.pipe(conn)
|
|
509
|
-
// make ora happy
|
|
510
|
-
// @ts-ignore
|
|
511
|
-
output.isTTY = true
|
|
512
|
-
// @ts-ignore
|
|
513
|
-
output.cursorTo = function cursorTo (x, y, callback) {
|
|
514
|
-
readline = readline || require('readline')
|
|
515
|
-
readline.cursorTo(this, x, y, callback)
|
|
516
|
-
}
|
|
517
|
-
// @ts-ignore
|
|
518
|
-
output.clearLine = function clearLine (dir, callback) {
|
|
519
|
-
readline = readline || require('readline')
|
|
520
|
-
readline.clearLine(this, dir, callback)
|
|
521
|
-
}
|
|
522
|
-
mutexFn(hasInput ? input : null, hasOutput ? output : null, ipcColorLevel)
|
|
523
|
-
.then(resolve, reject)
|
|
524
|
-
.finally(() => {
|
|
525
|
-
conn.unref()
|
|
526
|
-
conn.end()
|
|
527
|
-
input.end()
|
|
528
|
-
output.end()
|
|
529
|
-
// process.exit(13)
|
|
530
|
-
})
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
} catch (e) {
|
|
534
|
-
reject(e)
|
|
535
|
-
}
|
|
536
|
-
})
|
|
537
|
-
})
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
const pendingCaptures = []
|
|
542
|
-
let captured = false
|
|
543
|
-
const sock = path.join(require('os').tmpdir(), `socket-security-tty-${process.pid}.sock`)
|
|
544
|
-
process.env.SOCKET_SECURITY_TTY_IPC = sock
|
|
545
|
-
try {
|
|
546
|
-
await require('fs/promises').unlink(sock)
|
|
547
|
-
} catch (e) {
|
|
548
|
-
if (e.code !== 'ENOENT') {
|
|
549
|
-
throw e
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
process.on('exit', () => {
|
|
553
|
-
ttyServer.close()
|
|
554
|
-
try {
|
|
555
|
-
require('fs').unlinkSync(sock)
|
|
556
|
-
} catch (e) {
|
|
557
|
-
if (e.code !== 'ENOENT') {
|
|
558
|
-
throw e
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
})
|
|
562
|
-
const input = isSTDINInteractive ? process.stdin : null
|
|
563
|
-
const output = process.stderr
|
|
564
|
-
const ttyServer = await new Promise((resolve, reject) => {
|
|
565
|
-
const server = net.createServer(async (conn) => {
|
|
566
|
-
if (captured) {
|
|
567
|
-
const captured = new Promise((resolve) => {
|
|
568
|
-
pendingCaptures.push({
|
|
569
|
-
resolve
|
|
570
|
-
})
|
|
571
|
-
})
|
|
572
|
-
await captured
|
|
573
|
-
} else {
|
|
574
|
-
captured = true
|
|
575
|
-
}
|
|
576
|
-
const wasProgressEnabled = npmlog.progressEnabled
|
|
577
|
-
npmlog.pause()
|
|
578
|
-
if (wasProgressEnabled) {
|
|
579
|
-
npmlog.disableProgress()
|
|
580
|
-
}
|
|
581
|
-
conn.write(`${JSON.stringify({
|
|
582
|
-
ipc_version,
|
|
583
|
-
capabilities: {
|
|
584
|
-
input: Boolean(input),
|
|
585
|
-
output: true,
|
|
586
|
-
colorLevel
|
|
587
|
-
}
|
|
588
|
-
})}\n`)
|
|
589
|
-
conn.on('data', (data) => {
|
|
590
|
-
output.write(data)
|
|
591
|
-
})
|
|
592
|
-
conn.on('error', (e) => {
|
|
593
|
-
output.write(`there was an error prompting from a subshell (${e.message}), socket npm closing`)
|
|
594
|
-
process.exit(1)
|
|
595
|
-
})
|
|
596
|
-
input.on('data', (data) => {
|
|
597
|
-
conn.write(data)
|
|
598
|
-
})
|
|
599
|
-
input.on('end', () => {
|
|
600
|
-
conn.unref()
|
|
601
|
-
conn.end()
|
|
602
|
-
if (wasProgressEnabled) {
|
|
603
|
-
npmlog.enableProgress()
|
|
604
|
-
}
|
|
605
|
-
npmlog.resume()
|
|
606
|
-
nextCapture()
|
|
607
|
-
})
|
|
608
|
-
}).listen(sock, (err) => {
|
|
609
|
-
if (err) reject(err)
|
|
610
|
-
else resolve(server)
|
|
611
|
-
}).unref()
|
|
612
|
-
})
|
|
613
|
-
/**
|
|
614
|
-
*
|
|
615
|
-
*/
|
|
616
|
-
function nextCapture () {
|
|
617
|
-
if (pendingCaptures.length > 0) {
|
|
618
|
-
const nextCapture = pendingCaptures.shift()
|
|
619
|
-
nextCapture.resolve()
|
|
620
|
-
} else {
|
|
621
|
-
captured = false
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
return {
|
|
625
|
-
async captureTTY (mutexFn) {
|
|
626
|
-
if (captured) {
|
|
627
|
-
const captured = new Promise((resolve) => {
|
|
628
|
-
pendingCaptures.push({
|
|
629
|
-
resolve
|
|
630
|
-
})
|
|
631
|
-
})
|
|
632
|
-
await captured
|
|
633
|
-
} else {
|
|
634
|
-
captured = true
|
|
635
|
-
}
|
|
636
|
-
const wasProgressEnabled = npmlog.progressEnabled
|
|
637
|
-
try {
|
|
638
|
-
npmlog.pause()
|
|
639
|
-
if (wasProgressEnabled) {
|
|
640
|
-
npmlog.disableProgress()
|
|
641
|
-
}
|
|
642
|
-
// need await here for proper finally timing
|
|
643
|
-
return await mutexFn(input, output, colorLevel)
|
|
644
|
-
} finally {
|
|
645
|
-
if (wasProgressEnabled) {
|
|
646
|
-
npmlog.enableProgress()
|
|
647
|
-
}
|
|
648
|
-
npmlog.resume()
|
|
649
|
-
nextCapture()
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
}
|