@socketsecurity/cli 0.7.2 → 0.8.2
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 +2 -2
- package/lib/commands/info/index.js +3 -3
- package/lib/commands/login/index.js +121 -17
- package/lib/commands/logout/index.js +3 -0
- package/lib/commands/report/create.js +17 -16
- 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 +226 -255
- package/lib/shadow/tty-server.cjs +222 -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 +59 -48
- package/lib/utils/sdk.js +55 -11
- 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 -15
|
@@ -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,139 @@ 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 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(() => {})
|
|
39
175
|
|
|
40
176
|
// shadow `npm` and `npx` to mitigate subshells
|
|
41
177
|
require('./link.cjs')(fs.realpathSync(path.join(__dirname, 'bin')), 'npm')
|
|
@@ -76,6 +212,7 @@ async function * batchScan (
|
|
|
76
212
|
}
|
|
77
213
|
})
|
|
78
214
|
}
|
|
215
|
+
// TODO: migrate to SDK
|
|
79
216
|
const pkgDataReq = https.request(
|
|
80
217
|
'https://api.socket.dev/v0/scan/batch',
|
|
81
218
|
{
|
|
@@ -108,8 +245,10 @@ let translations = null
|
|
|
108
245
|
*/
|
|
109
246
|
let formatter = null
|
|
110
247
|
|
|
111
|
-
const ttyServerPromise = chalkPromise.then(chalk => {
|
|
112
|
-
return createTTYServer(chalk.default.level)
|
|
248
|
+
const ttyServerPromise = chalkPromise.then(async (chalk) => {
|
|
249
|
+
return createTTYServer(chalk.default.level, (await isInteractivePromise).default({
|
|
250
|
+
stream: process.stdin
|
|
251
|
+
}), npmlog)
|
|
113
252
|
})
|
|
114
253
|
|
|
115
254
|
const npmEntrypoint = fs.realpathSync(`${process.argv[1]}`)
|
|
@@ -130,6 +269,10 @@ function findRoot (filepath) {
|
|
|
130
269
|
const npmDir = findRoot(path.dirname(npmEntrypoint))
|
|
131
270
|
const arboristLibClassPath = path.join(npmDir, 'node_modules', '@npmcli', 'arborist', 'lib', 'arborist', 'index.js')
|
|
132
271
|
const npmlog = require(path.join(npmDir, 'node_modules', 'npmlog', 'lib', 'log.js'))
|
|
272
|
+
/**
|
|
273
|
+
* @type {import('pacote')}
|
|
274
|
+
*/
|
|
275
|
+
const pacote = require(path.join(npmDir, 'node_modules', 'pacote'))
|
|
133
276
|
|
|
134
277
|
/**
|
|
135
278
|
* @type {typeof import('@npmcli/arborist')}
|
|
@@ -178,7 +321,12 @@ class SafeArborist extends Arborist {
|
|
|
178
321
|
return this[kRiskyReify](...args)
|
|
179
322
|
}
|
|
180
323
|
args[0] ??= {}
|
|
181
|
-
const old = {
|
|
324
|
+
const old = {
|
|
325
|
+
dryRun: false,
|
|
326
|
+
save: Boolean(args[0].save ?? true),
|
|
327
|
+
saveBundle: Boolean(args[0].saveBundle ?? false),
|
|
328
|
+
...args[0]
|
|
329
|
+
}
|
|
182
330
|
// @ts-expect-error types are wrong
|
|
183
331
|
args[0].dryRun = true
|
|
184
332
|
args[0].save = false
|
|
@@ -197,7 +345,7 @@ class SafeArborist extends Arborist {
|
|
|
197
345
|
}
|
|
198
346
|
const ttyServer = await ttyServerPromise
|
|
199
347
|
const proceed = await ttyServer.captureTTY(async (input, output, colorLevel) => {
|
|
200
|
-
if (input) {
|
|
348
|
+
if (input && output) {
|
|
201
349
|
const chalkNS = await chalkPromise
|
|
202
350
|
chalkNS.default.level = colorLevel
|
|
203
351
|
const oraNS = await oraPromise
|
|
@@ -212,7 +360,7 @@ class SafeArborist extends Arborist {
|
|
|
212
360
|
spinner: oraNS.spinners.dots,
|
|
213
361
|
})
|
|
214
362
|
}
|
|
215
|
-
const risky = await packagesHaveRiskyIssues(this.registry, diff, ora, input, output)
|
|
363
|
+
const risky = await packagesHaveRiskyIssues(this, this.registry, diff, ora, input, output)
|
|
216
364
|
if (!risky) {
|
|
217
365
|
return true
|
|
218
366
|
}
|
|
@@ -244,11 +392,13 @@ class SafeArborist extends Arborist {
|
|
|
244
392
|
rli.close()
|
|
245
393
|
}
|
|
246
394
|
} else {
|
|
247
|
-
if (await packagesHaveRiskyIssues(this.registry, diff, null, null, output)) {
|
|
395
|
+
if (await packagesHaveRiskyIssues(this, this.registry, diff, null, null, output)) {
|
|
248
396
|
throw new Error('Socket npm Unable to prompt to accept risk, need TTY to do so')
|
|
249
397
|
}
|
|
250
398
|
return true
|
|
251
399
|
}
|
|
400
|
+
// @ts-ignore paranoia
|
|
401
|
+
// eslint-disable-next-line
|
|
252
402
|
return false
|
|
253
403
|
})
|
|
254
404
|
if (proceed) {
|
|
@@ -353,14 +503,15 @@ function walk (diff, needInfoOn = []) {
|
|
|
353
503
|
}
|
|
354
504
|
|
|
355
505
|
/**
|
|
356
|
-
* @param {
|
|
506
|
+
* @param {SafeArborist} safeArb
|
|
507
|
+
* @param {string} _registry
|
|
357
508
|
* @param {InstallEffect[]} pkgs
|
|
358
509
|
* @param {import('ora')['default'] | null} ora
|
|
359
|
-
* @param {Readable | null}
|
|
360
|
-
* @param {Writable}
|
|
510
|
+
* @param {Readable | null} [_input]
|
|
511
|
+
* @param {Writable | null} [output]
|
|
361
512
|
* @returns {Promise<boolean>}
|
|
362
513
|
*/
|
|
363
|
-
async function packagesHaveRiskyIssues (
|
|
514
|
+
async function packagesHaveRiskyIssues (safeArb, _registry, pkgs, ora = null, _input, output) {
|
|
364
515
|
let failed = false
|
|
365
516
|
if (pkgs.length) {
|
|
366
517
|
let remaining = pkgs.length
|
|
@@ -374,69 +525,81 @@ async function packagesHaveRiskyIssues (registry, pkgs, ora = null, input, outpu
|
|
|
374
525
|
const spinner = ora ? ora().start(getText()) : null
|
|
375
526
|
const pkgDatas = []
|
|
376
527
|
try {
|
|
528
|
+
// TODO: determine org based on cwd, pass in
|
|
529
|
+
const uxLookup = await uxLookupInit
|
|
530
|
+
|
|
377
531
|
for await (const pkgData of batchScan(pkgs.map(pkg => pkg.pkgid))) {
|
|
532
|
+
/**
|
|
533
|
+
* @type {Array<any>}
|
|
534
|
+
*/
|
|
378
535
|
let failures = []
|
|
536
|
+
let displayWarning = false
|
|
537
|
+
const name = pkgData.pkg
|
|
538
|
+
const version = pkgData.ver
|
|
539
|
+
let blocked = false
|
|
379
540
|
if (pkgData.type === 'missing') {
|
|
541
|
+
failed = true
|
|
380
542
|
failures.push({
|
|
381
543
|
type: 'missingDependency'
|
|
382
544
|
})
|
|
383
545
|
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
|
-
)
|
|
546
|
+
} else {
|
|
547
|
+
for (const failure of pkgData.value.issues) {
|
|
548
|
+
const ux = await uxLookup({ package: { name, version }, issue: { type: failure.type } })
|
|
549
|
+
if (ux.display || ux.block) {
|
|
550
|
+
failures.push({ raw: failure, block: ux.block })
|
|
551
|
+
// before we ask about problematic issues, check to see if they already existed in the old version
|
|
552
|
+
// if they did, be quiet
|
|
553
|
+
const pkg = pkgs.find(pkg => pkg.pkgid === `${pkgData.pkg}@${pkgData.ver}` && pkg.existing?.startsWith(pkgData.pkg + '@'))
|
|
554
|
+
if (pkg?.existing) {
|
|
555
|
+
for await (const oldPkgData of batchScan([pkg.existing])) {
|
|
556
|
+
if (oldPkgData.type === 'success') {
|
|
557
|
+
failures = failures.filter(
|
|
558
|
+
issue => oldPkgData.value.issues.find(oldIssue => oldIssue.type === issue.raw.type) == null
|
|
559
|
+
)
|
|
560
|
+
}
|
|
561
|
+
}
|
|
412
562
|
}
|
|
413
563
|
}
|
|
564
|
+
if (ux.block) {
|
|
565
|
+
failed = true
|
|
566
|
+
blocked = true
|
|
567
|
+
}
|
|
568
|
+
if (ux.display) {
|
|
569
|
+
displayWarning = true
|
|
570
|
+
}
|
|
414
571
|
}
|
|
415
572
|
}
|
|
416
|
-
if (
|
|
417
|
-
|
|
418
|
-
|
|
573
|
+
if (!blocked) {
|
|
574
|
+
const pkg = pkgs.find(pkg => pkg.pkgid === `${pkgData.pkg}@${pkgData.ver}`)
|
|
575
|
+
if (pkg) {
|
|
576
|
+
pacote.tarball.stream(pkg.pkgid, (stream) => {
|
|
577
|
+
stream.resume()
|
|
578
|
+
// @ts-ignore pacote does a naughty
|
|
579
|
+
return stream.promise()
|
|
580
|
+
}, { ...safeArb[kCtorArgs][0] })
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
if (displayWarning) {
|
|
419
584
|
translations ??= JSON.parse(fs.readFileSync(path.join(__dirname, '/translations.json'), 'utf-8'))
|
|
420
585
|
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
|
-
}
|
|
586
|
+
spinner?.stop()
|
|
587
|
+
output?.write(`(socket) ${formatter.hyperlink(`${name}@${version}`, `https://socket.dev/npm/package/${name}/overview/${version}`)} contains risks:\n`)
|
|
588
|
+
const lines = new Set()
|
|
589
|
+
for (const failure of failures.sort((a, b) => a.raw.type < b.raw.type ? -1 : 1)) {
|
|
590
|
+
const type = failure.raw.type
|
|
591
|
+
if (type) {
|
|
592
|
+
// @ts-ignore
|
|
593
|
+
const issueTypeTranslation = translations.issues[type]
|
|
594
|
+
// TODO: emoji seems to misalign terminals sometimes
|
|
595
|
+
// @ts-ignore
|
|
596
|
+
lines.add(` ${issueTypeTranslation?.title ?? type}${failure.block ? '' : ' (non-blocking)'} - ${issueTypeTranslation?.description ?? ''}\n`)
|
|
435
597
|
}
|
|
436
598
|
}
|
|
599
|
+
for (const line of lines) {
|
|
600
|
+
output?.write(line)
|
|
601
|
+
}
|
|
437
602
|
spinner?.start()
|
|
438
|
-
} else {
|
|
439
|
-
// TODO: have pacote/cacache download non-problematic files while waiting
|
|
440
603
|
}
|
|
441
604
|
remaining--
|
|
442
605
|
if (remaining !== 0) {
|
|
@@ -459,195 +622,3 @@ async function packagesHaveRiskyIssues (registry, pkgs, ora = null, input, outpu
|
|
|
459
622
|
return false
|
|
460
623
|
}
|
|
461
624
|
}
|
|
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
|
-
}
|