@socketsecurity/cli 0.5.4 → 0.7.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/lib/commands/index.js +2 -0
- package/lib/commands/login/index.js +66 -0
- package/lib/commands/logout/index.js +32 -0
- package/lib/commands/report/create.js +11 -1
- package/lib/shadow/npm-injection.cjs +351 -67
- package/lib/shadow/update-notifier.mjs +3 -0
- package/lib/utils/api-helpers.js +1 -1
- package/lib/utils/path-resolve.js +61 -48
- package/lib/utils/sdk.js +6 -7
- package/lib/utils/settings.js +57 -0
- package/package.json +6 -4
- package/lib/shadow/global.d.ts +0 -3
package/lib/commands/index.js
CHANGED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import isInteractive from 'is-interactive'
|
|
2
|
+
import meow from 'meow'
|
|
3
|
+
import ora from 'ora'
|
|
4
|
+
import prompts from 'prompts'
|
|
5
|
+
|
|
6
|
+
import { ChalkOrMarkdown } from '../../utils/chalk-markdown.js'
|
|
7
|
+
import { AuthError, InputError } from '../../utils/errors.js'
|
|
8
|
+
import { setupSdk } from '../../utils/sdk.js'
|
|
9
|
+
import { getSetting, updateSetting } from '../../utils/settings.js'
|
|
10
|
+
|
|
11
|
+
const description = 'Socket API login'
|
|
12
|
+
|
|
13
|
+
/** @type {import('../../utils/meow-with-subcommands').CliSubcommand} */
|
|
14
|
+
export const login = {
|
|
15
|
+
description,
|
|
16
|
+
run: async (argv, importMeta, { parentName }) => {
|
|
17
|
+
const name = parentName + ' login'
|
|
18
|
+
const cli = meow(`
|
|
19
|
+
Usage
|
|
20
|
+
$ ${name}
|
|
21
|
+
|
|
22
|
+
Logs into the Socket API by prompting for an API key
|
|
23
|
+
|
|
24
|
+
Examples
|
|
25
|
+
$ ${name}
|
|
26
|
+
`, {
|
|
27
|
+
argv,
|
|
28
|
+
description,
|
|
29
|
+
importMeta,
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
if (cli.input.length) cli.showHelp()
|
|
33
|
+
|
|
34
|
+
if (!isInteractive()) {
|
|
35
|
+
throw new InputError('cannot prompt for credentials in a non-interactive shell')
|
|
36
|
+
}
|
|
37
|
+
const format = new ChalkOrMarkdown(false)
|
|
38
|
+
const { apiKey } = await prompts({
|
|
39
|
+
type: 'password',
|
|
40
|
+
name: 'apiKey',
|
|
41
|
+
message: `Enter your ${format.hyperlink(
|
|
42
|
+
'Socket.dev API key',
|
|
43
|
+
'https://docs.socket.dev/docs/api-keys'
|
|
44
|
+
)}`,
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
if (!apiKey) {
|
|
48
|
+
ora('API key not updated').warn()
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const spinner = ora('Verifying API key...').start()
|
|
53
|
+
|
|
54
|
+
const oldKey = getSetting('apiKey')
|
|
55
|
+
updateSetting('apiKey', apiKey)
|
|
56
|
+
try {
|
|
57
|
+
const sdk = await setupSdk()
|
|
58
|
+
const quota = await sdk.getQuota()
|
|
59
|
+
if (!quota.success) throw new AuthError()
|
|
60
|
+
spinner.succeed(`API key ${oldKey ? 'updated' : 'set'}`)
|
|
61
|
+
} catch (e) {
|
|
62
|
+
updateSetting('apiKey', oldKey)
|
|
63
|
+
spinner.fail('Invalid API key')
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import meow from 'meow'
|
|
2
|
+
import ora from 'ora'
|
|
3
|
+
|
|
4
|
+
import { updateSetting } from '../../utils/settings.js'
|
|
5
|
+
|
|
6
|
+
const description = 'Socket API logout'
|
|
7
|
+
|
|
8
|
+
/** @type {import('../../utils/meow-with-subcommands').CliSubcommand} */
|
|
9
|
+
export const logout = {
|
|
10
|
+
description,
|
|
11
|
+
run: async (argv, importMeta, { parentName }) => {
|
|
12
|
+
const name = parentName + ' logout'
|
|
13
|
+
const cli = meow(`
|
|
14
|
+
Usage
|
|
15
|
+
$ ${name}
|
|
16
|
+
|
|
17
|
+
Logs out of the Socket API and clears all Socket credentials from disk
|
|
18
|
+
|
|
19
|
+
Examples
|
|
20
|
+
$ ${name}
|
|
21
|
+
`, {
|
|
22
|
+
argv,
|
|
23
|
+
description,
|
|
24
|
+
importMeta,
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
if (cli.input.length) cli.showHelp()
|
|
28
|
+
|
|
29
|
+
updateSetting('apiKey', null)
|
|
30
|
+
ora('Successfully logged out').succeed()
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -181,7 +181,17 @@ async function setupCommand (name, description, argv, importMeta) {
|
|
|
181
181
|
}
|
|
182
182
|
})
|
|
183
183
|
|
|
184
|
-
|
|
184
|
+
// TODO: setupSdk(getDefaultKey() || FREE_API_KEY)
|
|
185
|
+
const socketSdk = await setupSdk()
|
|
186
|
+
const supportedFiles = await socketSdk.getReportSupportedFiles()
|
|
187
|
+
.then(res => {
|
|
188
|
+
if (!res.success) handleUnsuccessfulApiResponse('getReportSupportedFiles', res, ora())
|
|
189
|
+
return res.data
|
|
190
|
+
}).catch(cause => {
|
|
191
|
+
throw new ErrorWithCause('Failed getting supported files for report', { cause })
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
const packagePaths = await getPackageFiles(cwd, cli.input, config, supportedFiles, debugLog)
|
|
185
195
|
|
|
186
196
|
return {
|
|
187
197
|
config,
|
|
@@ -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,59 @@ 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 settingsPromise = import('../utils/settings.js')
|
|
16
|
+
const ipc_version = require('../../package.json').version
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
// due to update-notifier pkg being ESM only we actually spawn a subprocess sadly
|
|
20
|
+
require('child_process').spawnSync(process.execPath, [
|
|
21
|
+
path.join(__dirname, 'update-notifier.mjs')
|
|
22
|
+
], {
|
|
23
|
+
stdio: 'inherit'
|
|
24
|
+
})
|
|
25
|
+
} catch (e) {
|
|
26
|
+
// ignore if update notification fails
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @typedef {import('stream').Readable} Readable
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* @typedef {import('stream').Writable} Writable
|
|
34
|
+
*/
|
|
13
35
|
|
|
14
|
-
const
|
|
36
|
+
const pubTokenPromise = settingsPromise.then(({ getSetting }) =>
|
|
37
|
+
getSetting('apiKey') || 'sktsec_t_--RAN5U4ivauy4w37-6aoKyYPDt5ZbaT5JBVMqiwKo_api'
|
|
38
|
+
);
|
|
15
39
|
|
|
16
40
|
// shadow `npm` and `npx` to mitigate subshells
|
|
17
41
|
require('./link.cjs')(fs.realpathSync(path.join(__dirname, 'bin')), 'npm')
|
|
18
42
|
|
|
43
|
+
/**
|
|
44
|
+
*
|
|
45
|
+
* @param {string} pkgid
|
|
46
|
+
* @returns {{name: string, version: string}}
|
|
47
|
+
*/
|
|
48
|
+
const pkgidParts = (pkgid) => {
|
|
49
|
+
const delimiter = pkgid.lastIndexOf('@')
|
|
50
|
+
const name = pkgid.slice(0, delimiter)
|
|
51
|
+
const version = pkgid.slice(delimiter + 1)
|
|
52
|
+
return { name, version }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @typedef PURLParts
|
|
57
|
+
* @property {'npm'} type
|
|
58
|
+
* @property {string} namespace_and_name
|
|
59
|
+
* @property {string} version
|
|
60
|
+
* @property {URL['href']} repository_url
|
|
61
|
+
*/
|
|
62
|
+
|
|
19
63
|
/**
|
|
20
64
|
* @param {string[]} pkgids
|
|
21
65
|
* @returns {AsyncGenerator<{eco: string, pkg: string, ver: string } & ({type: 'missing'} | {type: 'success', value: { issues: any[] }})>}
|
|
@@ -23,11 +67,10 @@ require('./link.cjs')(fs.realpathSync(path.join(__dirname, 'bin')), 'npm')
|
|
|
23
67
|
async function * batchScan (
|
|
24
68
|
pkgids
|
|
25
69
|
) {
|
|
70
|
+
const pubToken = await pubTokenPromise
|
|
26
71
|
const query = {
|
|
27
72
|
packages: pkgids.map(pkgid => {
|
|
28
|
-
const
|
|
29
|
-
const name = pkgid.slice(0, delimiter)
|
|
30
|
-
const version = pkgid.slice(delimiter + 1)
|
|
73
|
+
const { name, version } = pkgidParts(pkgid)
|
|
31
74
|
return {
|
|
32
75
|
eco: 'npm', pkg: name, ver: version, top: true
|
|
33
76
|
}
|
|
@@ -65,6 +108,10 @@ let translations = null
|
|
|
65
108
|
*/
|
|
66
109
|
let formatter = null
|
|
67
110
|
|
|
111
|
+
const ttyServerPromise = chalkPromise.then(chalk => {
|
|
112
|
+
return createTTYServer(chalk.default.level)
|
|
113
|
+
})
|
|
114
|
+
|
|
68
115
|
const npmEntrypoint = fs.realpathSync(`${process.argv[1]}`)
|
|
69
116
|
/**
|
|
70
117
|
* @param {string} filepath
|
|
@@ -82,6 +129,8 @@ function findRoot (filepath) {
|
|
|
82
129
|
}
|
|
83
130
|
const npmDir = findRoot(path.dirname(npmEntrypoint))
|
|
84
131
|
const arboristLibClassPath = path.join(npmDir, 'node_modules', '@npmcli', 'arborist', 'lib', 'arborist', 'index.js')
|
|
132
|
+
const npmlog = require(path.join(npmDir, 'node_modules', 'npmlog', 'lib', 'log.js'))
|
|
133
|
+
|
|
85
134
|
/**
|
|
86
135
|
* @type {typeof import('@npmcli/arborist')}
|
|
87
136
|
*/
|
|
@@ -96,11 +145,11 @@ class SafeArborist extends Arborist {
|
|
|
96
145
|
constructor (...ctorArgs) {
|
|
97
146
|
const mutedArguments = [{
|
|
98
147
|
...(ctorArgs[0] ?? {}),
|
|
148
|
+
audit: true,
|
|
99
149
|
dryRun: true,
|
|
100
150
|
ignoreScripts: true,
|
|
101
151
|
save: false,
|
|
102
152
|
saveBundle: false,
|
|
103
|
-
audit: false,
|
|
104
153
|
// progress: false,
|
|
105
154
|
fund: false
|
|
106
155
|
}, ctorArgs.slice(1)]
|
|
@@ -140,39 +189,72 @@ class SafeArborist extends Arborist {
|
|
|
140
189
|
const diff = gatherDiff(this)
|
|
141
190
|
// @ts-expect-error types are wrong
|
|
142
191
|
args[0].dryRun = old.dryRun
|
|
143
|
-
// @ts-expect-error types are wrong
|
|
144
192
|
args[0].save = old.save
|
|
145
|
-
// @ts-expect-error types are wrong
|
|
146
193
|
args[0].saveBundle = old.saveBundle
|
|
147
|
-
// nothing to check, mmm already installed?
|
|
148
|
-
if (diff.
|
|
194
|
+
// nothing to check, mmm already installed or all private?
|
|
195
|
+
if (diff.findIndex(c => c.newPackage.repository_url === 'https://registry.npmjs.org') === -1) {
|
|
149
196
|
return this[kRiskyReify](...args)
|
|
150
197
|
}
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
198
|
+
const ttyServer = await ttyServerPromise
|
|
199
|
+
const proceed = await ttyServer.captureTTY(async (input, output, colorLevel) => {
|
|
200
|
+
if (input) {
|
|
201
|
+
const chalkNS = await chalkPromise
|
|
202
|
+
chalkNS.default.level = colorLevel
|
|
203
|
+
const oraNS = await oraPromise
|
|
204
|
+
const ora = () => {
|
|
205
|
+
return oraNS.default({
|
|
206
|
+
stream: output,
|
|
207
|
+
color: 'cyan',
|
|
208
|
+
isEnabled: true,
|
|
209
|
+
isSilent: false,
|
|
210
|
+
hideCursor: true,
|
|
211
|
+
discardStdin: true,
|
|
212
|
+
spinner: oraNS.spinners.dots,
|
|
164
213
|
})
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
214
|
+
}
|
|
215
|
+
const risky = await packagesHaveRiskyIssues(this.registry, diff, ora, input, output)
|
|
216
|
+
if (!risky) {
|
|
217
|
+
return true
|
|
218
|
+
}
|
|
219
|
+
const rl = require('readline')
|
|
220
|
+
const rlin = new PassThrough()
|
|
221
|
+
input.pipe(rlin, {
|
|
222
|
+
end: true
|
|
223
|
+
})
|
|
224
|
+
const rlout = new PassThrough()
|
|
225
|
+
rlout.pipe(output, {
|
|
226
|
+
end: false
|
|
227
|
+
})
|
|
228
|
+
const rli = rl.createInterface(rlin, rlout)
|
|
229
|
+
try {
|
|
230
|
+
while (true) {
|
|
231
|
+
/**
|
|
232
|
+
* @type {string}
|
|
233
|
+
*/
|
|
234
|
+
const answer = await new Promise((resolve) => {
|
|
235
|
+
rli.question('Accept risks of installing these packages (y/N)? ', (str) => resolve(str))
|
|
236
|
+
})
|
|
237
|
+
if (/^\s*y(es)?\s*$/i.test(answer)) {
|
|
238
|
+
return true
|
|
239
|
+
} else if (/^(\s*no?\s*|)$/i.test(answer)) {
|
|
240
|
+
return false
|
|
241
|
+
}
|
|
169
242
|
}
|
|
243
|
+
} finally {
|
|
244
|
+
rli.close()
|
|
245
|
+
}
|
|
246
|
+
} else {
|
|
247
|
+
if (await packagesHaveRiskyIssues(this.registry, diff, null, null, output)) {
|
|
248
|
+
throw new Error('Socket npm Unable to prompt to accept risk, need TTY to do so')
|
|
170
249
|
}
|
|
250
|
+
return true
|
|
171
251
|
}
|
|
252
|
+
return false
|
|
253
|
+
})
|
|
254
|
+
if (proceed) {
|
|
172
255
|
return this[kRiskyReify](...args)
|
|
173
256
|
} else {
|
|
174
|
-
|
|
175
|
-
throw new Error('Socket npm Unable to prompt to accept risk, need TTY to do so')
|
|
257
|
+
throw new Error('Socket npm exiting due to risks')
|
|
176
258
|
}
|
|
177
259
|
}
|
|
178
260
|
}
|
|
@@ -180,34 +262,18 @@ class SafeArborist extends Arborist {
|
|
|
180
262
|
require.cache[arboristLibClassPath].exports = SafeArborist
|
|
181
263
|
|
|
182
264
|
/**
|
|
183
|
-
* @
|
|
184
|
-
* @returns {{
|
|
265
|
+
* @typedef {{
|
|
185
266
|
* check: InstallEffect[],
|
|
186
267
|
* unknowns: InstallEffect[]
|
|
187
|
-
* }}
|
|
268
|
+
* }} InstallDiff
|
|
269
|
+
*/
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* @param {InstanceType<typeof Arborist>} arb
|
|
273
|
+
* @returns {InstallEffect[]}
|
|
188
274
|
*/
|
|
189
275
|
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
|
-
}
|
|
276
|
+
return walk(arb.diff)
|
|
211
277
|
}
|
|
212
278
|
/**
|
|
213
279
|
* @typedef InstallEffect
|
|
@@ -216,6 +282,8 @@ function gatherDiff (arb) {
|
|
|
216
282
|
* @property {import('@npmcli/arborist').Node['pkgid']} pkgid
|
|
217
283
|
* @property {import('@npmcli/arborist').Node['resolved']} resolved
|
|
218
284
|
* @property {import('@npmcli/arborist').Node['location']} location
|
|
285
|
+
* @property {PURLParts | null} oldPackage
|
|
286
|
+
* @property {PURLParts} newPackage
|
|
219
287
|
*/
|
|
220
288
|
/**
|
|
221
289
|
* @param {import('@npmcli/arborist').Diff | null} diff
|
|
@@ -243,13 +311,36 @@ function walk (diff, needInfoOn = []) {
|
|
|
243
311
|
}
|
|
244
312
|
if (keep) {
|
|
245
313
|
if (diff.ideal?.pkgid) {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
314
|
+
/**
|
|
315
|
+
*
|
|
316
|
+
* @param {string} pkgid - `pkg@ver`
|
|
317
|
+
* @param {string} resolved - tarball link, should match `/name/-/name-ver.tgz` as tail, used to obtain repository_url
|
|
318
|
+
* @returns {PURLParts}
|
|
319
|
+
*/
|
|
320
|
+
function toPURL (pkgid, resolved) {
|
|
321
|
+
const repo = resolved
|
|
322
|
+
.replace(/#[\s\S]*$/u, '')
|
|
323
|
+
.replace(/\?[\s\S]*$/u, '')
|
|
324
|
+
.replace(/\/[^/]*\/-\/[\s\S]*$/u, '')
|
|
325
|
+
const { name, version } = pkgidParts(pkgid)
|
|
326
|
+
return {
|
|
327
|
+
type: 'npm',
|
|
328
|
+
namespace_and_name: name,
|
|
329
|
+
version,
|
|
330
|
+
repository_url: repo
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
if (diff.ideal.resolved && (!diff.actual || diff.actual.resolved)) {
|
|
334
|
+
needInfoOn.push({
|
|
335
|
+
existing,
|
|
336
|
+
action: diff.action,
|
|
337
|
+
location: diff.ideal.location,
|
|
338
|
+
pkgid: diff.ideal.pkgid,
|
|
339
|
+
newPackage: toPURL(diff.ideal.pkgid, diff.ideal.resolved),
|
|
340
|
+
oldPackage: diff.actual && diff.actual.resolved ? toPURL(diff.actual.pkgid, diff.actual.resolved) : null,
|
|
341
|
+
resolved: diff.ideal.resolved,
|
|
342
|
+
})
|
|
343
|
+
}
|
|
253
344
|
}
|
|
254
345
|
}
|
|
255
346
|
}
|
|
@@ -262,11 +353,14 @@ function walk (diff, needInfoOn = []) {
|
|
|
262
353
|
}
|
|
263
354
|
|
|
264
355
|
/**
|
|
356
|
+
* @param {string} registry
|
|
265
357
|
* @param {InstallEffect[]} pkgs
|
|
266
358
|
* @param {import('ora')['default'] | null} ora
|
|
359
|
+
* @param {Readable | null} input
|
|
360
|
+
* @param {Writable} ora
|
|
267
361
|
* @returns {Promise<boolean>}
|
|
268
362
|
*/
|
|
269
|
-
async function packagesHaveRiskyIssues (pkgs, ora = null) {
|
|
363
|
+
async function packagesHaveRiskyIssues (registry, pkgs, ora = null, input, output) {
|
|
270
364
|
let failed = false
|
|
271
365
|
if (pkgs.length) {
|
|
272
366
|
let remaining = pkgs.length
|
|
@@ -277,7 +371,7 @@ async function packagesHaveRiskyIssues (pkgs, ora = null) {
|
|
|
277
371
|
function getText () {
|
|
278
372
|
return `Looking up data for ${remaining} packages`
|
|
279
373
|
}
|
|
280
|
-
const spinner = ora ? ora(
|
|
374
|
+
const spinner = ora ? ora().start(getText()) : null
|
|
281
375
|
const pkgDatas = []
|
|
282
376
|
try {
|
|
283
377
|
for await (const pkgData of batchScan(pkgs.map(pkg => pkg.pkgid))) {
|
|
@@ -326,7 +420,7 @@ async function packagesHaveRiskyIssues (pkgs, ora = null) {
|
|
|
326
420
|
formatter ??= new ((await chalkMarkdownPromise).ChalkOrMarkdown)(false)
|
|
327
421
|
const name = pkgData.pkg
|
|
328
422
|
const version = pkgData.ver
|
|
329
|
-
|
|
423
|
+
output.write(`(socket) ${formatter.hyperlink(`${name}@${version}`, `https://socket.dev/npm/package/${name}/overview/${version}`)} contains risks:\n`)
|
|
330
424
|
if (translations) {
|
|
331
425
|
for (const failure of failures) {
|
|
332
426
|
const type = failure.type
|
|
@@ -335,8 +429,8 @@ async function packagesHaveRiskyIssues (pkgs, ora = null) {
|
|
|
335
429
|
const issueTypeTranslation = translations.issues[type]
|
|
336
430
|
// TODO: emoji seems to misalign terminals sometimes
|
|
337
431
|
// @ts-ignore
|
|
338
|
-
const msg = ` ${issueTypeTranslation.title} - ${issueTypeTranslation.description}`
|
|
339
|
-
|
|
432
|
+
const msg = ` ${issueTypeTranslation.title} - ${issueTypeTranslation.description}\n`
|
|
433
|
+
output.write(msg)
|
|
340
434
|
}
|
|
341
435
|
}
|
|
342
436
|
}
|
|
@@ -349,8 +443,6 @@ async function packagesHaveRiskyIssues (pkgs, ora = null) {
|
|
|
349
443
|
if (spinner) {
|
|
350
444
|
spinner.text = getText()
|
|
351
445
|
}
|
|
352
|
-
} else {
|
|
353
|
-
spinner?.stop()
|
|
354
446
|
}
|
|
355
447
|
pkgDatas.push(pkgData)
|
|
356
448
|
}
|
|
@@ -367,3 +459,195 @@ async function packagesHaveRiskyIssues (pkgs, ora = null) {
|
|
|
367
459
|
return false
|
|
368
460
|
}
|
|
369
461
|
}
|
|
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
|
+
}
|
package/lib/utils/api-helpers.js
CHANGED
|
@@ -8,7 +8,7 @@ import { AuthError } from './errors.js'
|
|
|
8
8
|
* @param {T} _name
|
|
9
9
|
* @param {import('@socketsecurity/sdk').SocketSdkErrorType<T>} result
|
|
10
10
|
* @param {import('ora').Ora} spinner
|
|
11
|
-
* @returns {
|
|
11
|
+
* @returns {never}
|
|
12
12
|
*/
|
|
13
13
|
export function handleUnsuccessfulApiResponse (_name, result, spinner) {
|
|
14
14
|
const resultError = 'error' in result && result.error && typeof result.error === 'object' ? result.error : {}
|
|
@@ -5,17 +5,12 @@ import { globby } from 'globby'
|
|
|
5
5
|
import ignore from 'ignore'
|
|
6
6
|
// @ts-ignore This package provides no types
|
|
7
7
|
import { directories } from 'ignore-by-default'
|
|
8
|
+
import micromatch from 'micromatch'
|
|
8
9
|
import { ErrorWithCause } from 'pony-cause'
|
|
9
10
|
|
|
10
11
|
import { InputError } from './errors.js'
|
|
11
12
|
import { isErrnoException } from './type-helpers.js'
|
|
12
13
|
|
|
13
|
-
/** @type {readonly string[]} */
|
|
14
|
-
const SUPPORTED_LOCKFILES = [
|
|
15
|
-
'package-lock.json',
|
|
16
|
-
'yarn.lock',
|
|
17
|
-
]
|
|
18
|
-
|
|
19
14
|
/**
|
|
20
15
|
* There are a lot of possible folders that we should not be looking in and "ignore-by-default" helps us with defining those
|
|
21
16
|
*
|
|
@@ -23,10 +18,17 @@ const SUPPORTED_LOCKFILES = [
|
|
|
23
18
|
*/
|
|
24
19
|
const ignoreByDefault = directories()
|
|
25
20
|
|
|
26
|
-
/** @type {
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
21
|
+
/** @type {import('globby').Options} */
|
|
22
|
+
const BASE_GLOBBY_OPTS = {
|
|
23
|
+
absolute: true,
|
|
24
|
+
expandDirectories: false,
|
|
25
|
+
gitignore: true,
|
|
26
|
+
ignore: [
|
|
27
|
+
...ignoreByDefault.map(item => '**/' + item)
|
|
28
|
+
],
|
|
29
|
+
markDirectories: true,
|
|
30
|
+
unique: true,
|
|
31
|
+
}
|
|
30
32
|
|
|
31
33
|
/**
|
|
32
34
|
* Resolves package.json and lockfiles from (globbed) input paths, applying relevant ignores
|
|
@@ -34,28 +36,24 @@ const GLOB_IGNORE = [
|
|
|
34
36
|
* @param {string} cwd The working directory to use when resolving paths
|
|
35
37
|
* @param {string[]} inputPaths A list of paths to folders, package.json files and/or recognized lockfiles. Supports globs.
|
|
36
38
|
* @param {import('@socketsecurity/config').SocketYml|undefined} config
|
|
39
|
+
* @param {import('@socketsecurity/sdk').SocketSdkReturnType<"getReportSupportedFiles">['data']} supportedFiles
|
|
37
40
|
* @param {typeof console.error} debugLog
|
|
38
41
|
* @returns {Promise<string[]>}
|
|
39
42
|
* @throws {InputError}
|
|
40
43
|
*/
|
|
41
|
-
export async function getPackageFiles (cwd, inputPaths, config, debugLog) {
|
|
44
|
+
export async function getPackageFiles (cwd, inputPaths, config, supportedFiles, debugLog) {
|
|
42
45
|
debugLog(`Globbed resolving ${inputPaths.length} paths:`, inputPaths)
|
|
43
46
|
|
|
44
47
|
// TODO: Does not support `~/` paths
|
|
45
48
|
const entries = await globby(inputPaths, {
|
|
46
|
-
|
|
49
|
+
...BASE_GLOBBY_OPTS,
|
|
47
50
|
cwd,
|
|
48
|
-
|
|
49
|
-
gitignore: true,
|
|
50
|
-
ignore: [...GLOB_IGNORE],
|
|
51
|
-
markDirectories: true,
|
|
52
|
-
onlyFiles: false,
|
|
53
|
-
unique: true,
|
|
51
|
+
onlyFiles: false
|
|
54
52
|
})
|
|
55
53
|
|
|
56
54
|
debugLog(`Globbed resolved ${inputPaths.length} paths to ${entries.length} paths:`, entries)
|
|
57
55
|
|
|
58
|
-
const packageFiles = await mapGlobResultToFiles(entries)
|
|
56
|
+
const packageFiles = await mapGlobResultToFiles(entries, supportedFiles)
|
|
59
57
|
|
|
60
58
|
debugLog(`Mapped ${entries.length} entries to ${packageFiles.length} files:`, packageFiles)
|
|
61
59
|
|
|
@@ -73,11 +71,14 @@ export async function getPackageFiles (cwd, inputPaths, config, debugLog) {
|
|
|
73
71
|
* Takes paths to folders, package.json and/or recognized lock files and resolves them to package.json + lockfile pairs (where possible)
|
|
74
72
|
*
|
|
75
73
|
* @param {string[]} entries
|
|
74
|
+
* @param {import('@socketsecurity/sdk').SocketSdkReturnType<"getReportSupportedFiles">['data']} supportedFiles
|
|
76
75
|
* @returns {Promise<string[]>}
|
|
77
76
|
* @throws {InputError}
|
|
78
77
|
*/
|
|
79
|
-
export async function mapGlobResultToFiles (entries) {
|
|
80
|
-
const packageFiles = await Promise.all(
|
|
78
|
+
export async function mapGlobResultToFiles (entries, supportedFiles) {
|
|
79
|
+
const packageFiles = await Promise.all(
|
|
80
|
+
entries.map(entry => mapGlobEntryToFiles(entry, supportedFiles))
|
|
81
|
+
)
|
|
81
82
|
|
|
82
83
|
const uniquePackageFiles = [...new Set(packageFiles.flat())]
|
|
83
84
|
|
|
@@ -88,46 +89,58 @@ export async function mapGlobResultToFiles (entries) {
|
|
|
88
89
|
* Takes a single path to a folder, package.json or a recognized lock file and resolves to a package.json + lockfile pair (where possible)
|
|
89
90
|
*
|
|
90
91
|
* @param {string} entry
|
|
92
|
+
* @param {import('@socketsecurity/sdk').SocketSdkReturnType<'getReportSupportedFiles'>['data']} supportedFiles
|
|
91
93
|
* @returns {Promise<string[]>}
|
|
92
94
|
* @throws {InputError}
|
|
93
95
|
*/
|
|
94
|
-
export async function mapGlobEntryToFiles (entry) {
|
|
96
|
+
export async function mapGlobEntryToFiles (entry, supportedFiles) {
|
|
95
97
|
/** @type {string|undefined} */
|
|
96
|
-
let
|
|
97
|
-
/** @type {string
|
|
98
|
-
let
|
|
99
|
-
|
|
98
|
+
let pkgJSFile
|
|
99
|
+
/** @type {string[]} */
|
|
100
|
+
let jsLockFiles = []
|
|
101
|
+
/** @type {string[]} */
|
|
102
|
+
let pyFiles = []
|
|
103
|
+
|
|
104
|
+
const jsSupported = supportedFiles['npm'] || {}
|
|
105
|
+
const jsLockFilePatterns = Object.keys(jsSupported)
|
|
106
|
+
.filter(key => key !== 'packagejson')
|
|
107
|
+
.map(key => /** @type {{ pattern: string }} */ (jsSupported[key]).pattern)
|
|
108
|
+
|
|
109
|
+
const pyFilePatterns = Object.values(supportedFiles['pypi'] || {}).map(p => p.pattern)
|
|
100
110
|
if (entry.endsWith('/')) {
|
|
101
111
|
// If the match is a folder and that folder contains a package.json file, then include it
|
|
102
112
|
const filePath = path.resolve(entry, 'package.json')
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
113
|
+
if (await fileExists(filePath)) pkgJSFile = filePath
|
|
114
|
+
pyFiles = await globby(pyFilePatterns, {
|
|
115
|
+
...BASE_GLOBBY_OPTS,
|
|
116
|
+
cwd: entry
|
|
117
|
+
})
|
|
118
|
+
} else {
|
|
119
|
+
const entryFile = path.basename(entry)
|
|
120
|
+
|
|
121
|
+
if (entryFile === 'package.json') {
|
|
122
|
+
// If the match is a package.json file, then include it
|
|
123
|
+
pkgJSFile = entry
|
|
124
|
+
} else if (micromatch.isMatch(entryFile, jsLockFilePatterns)) {
|
|
125
|
+
jsLockFiles = [entry]
|
|
126
|
+
pkgJSFile = path.resolve(path.dirname(entry), 'package.json')
|
|
127
|
+
if (!(await fileExists(pkgJSFile))) return []
|
|
128
|
+
} else if (micromatch.isMatch(entryFile, pyFilePatterns)) {
|
|
129
|
+
pyFiles = [entry]
|
|
130
|
+
}
|
|
111
131
|
}
|
|
112
132
|
|
|
113
133
|
// If we will include a package.json file but don't already have a corresponding lockfile, then look for one
|
|
114
|
-
if (!
|
|
115
|
-
const pkgDir = path.dirname(
|
|
116
|
-
|
|
117
|
-
for (const name of SUPPORTED_LOCKFILES) {
|
|
118
|
-
const lockFileAlternative = path.resolve(pkgDir, name)
|
|
119
|
-
if (await fileExists(lockFileAlternative)) {
|
|
120
|
-
lockFile = lockFileAlternative
|
|
121
|
-
break
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
134
|
+
if (!jsLockFiles.length && pkgJSFile) {
|
|
135
|
+
const pkgDir = path.dirname(pkgJSFile)
|
|
125
136
|
|
|
126
|
-
|
|
127
|
-
|
|
137
|
+
jsLockFiles = await globby(jsLockFilePatterns, {
|
|
138
|
+
...BASE_GLOBBY_OPTS,
|
|
139
|
+
cwd: pkgDir
|
|
140
|
+
})
|
|
128
141
|
}
|
|
129
142
|
|
|
130
|
-
return
|
|
143
|
+
return [...jsLockFiles, ...pyFiles].concat(pkgJSFile ? [pkgJSFile] : [])
|
|
131
144
|
}
|
|
132
145
|
|
|
133
146
|
/**
|
package/lib/utils/sdk.js
CHANGED
|
@@ -7,28 +7,27 @@ import isInteractive from 'is-interactive'
|
|
|
7
7
|
import prompts from 'prompts'
|
|
8
8
|
|
|
9
9
|
import { AuthError } from './errors.js'
|
|
10
|
+
import { getSetting } from './settings.js'
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
|
-
*
|
|
13
|
+
* This API key should be stored globally for the duration of the CLI execution
|
|
13
14
|
*
|
|
14
15
|
* @type {string | undefined}
|
|
15
16
|
*/
|
|
16
|
-
let
|
|
17
|
+
let sessionAPIKey
|
|
17
18
|
|
|
18
19
|
/** @returns {Promise<import('@socketsecurity/sdk').SocketSdk>} */
|
|
19
20
|
export async function setupSdk () {
|
|
20
|
-
|
|
21
|
-
apiKey = process.env['SOCKET_SECURITY_API_KEY']
|
|
22
|
-
}
|
|
21
|
+
let apiKey = getSetting('apiKey') || process.env['SOCKET_SECURITY_API_KEY'] || sessionAPIKey
|
|
23
22
|
|
|
24
23
|
if (!apiKey && isInteractive()) {
|
|
25
24
|
const input = await prompts({
|
|
26
25
|
type: 'password',
|
|
27
26
|
name: 'apiKey',
|
|
28
|
-
message: 'Enter your Socket.dev API key',
|
|
27
|
+
message: 'Enter your Socket.dev API key (not saved)',
|
|
29
28
|
})
|
|
30
29
|
|
|
31
|
-
apiKey = input.apiKey
|
|
30
|
+
apiKey = sessionAPIKey = input.apiKey
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
if (!apiKey) {
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as fs from 'fs'
|
|
2
|
+
import * as os from 'os'
|
|
3
|
+
import * as path from 'path'
|
|
4
|
+
|
|
5
|
+
import ora from 'ora'
|
|
6
|
+
|
|
7
|
+
let dataHome = process.platform === 'win32'
|
|
8
|
+
? process.env['LOCALAPPDATA']
|
|
9
|
+
: process.env['XDG_DATA_HOME']
|
|
10
|
+
|
|
11
|
+
if (!dataHome) {
|
|
12
|
+
if (process.platform === 'win32') throw new Error('missing %LOCALAPPDATA%')
|
|
13
|
+
const home = os.homedir()
|
|
14
|
+
dataHome = path.join(home, ...(process.platform === 'darwin'
|
|
15
|
+
? ['Library', 'Application Support']
|
|
16
|
+
: ['.local', 'share']
|
|
17
|
+
))
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const settingsPath = path.join(dataHome, 'socket', 'settings')
|
|
21
|
+
|
|
22
|
+
/** @type {{apiKey?: string | null}} */
|
|
23
|
+
let settings = {}
|
|
24
|
+
|
|
25
|
+
if (fs.existsSync(settingsPath)) {
|
|
26
|
+
const raw = fs.readFileSync(settingsPath, 'utf-8')
|
|
27
|
+
try {
|
|
28
|
+
settings = JSON.parse(Buffer.from(raw, 'base64').toString())
|
|
29
|
+
} catch (e) {
|
|
30
|
+
ora(`Failed to parse settings at ${settingsPath}`).warn()
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
fs.mkdirSync(path.dirname(settingsPath), { recursive: true })
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @template {keyof typeof settings} Key
|
|
38
|
+
* @param {Key} key
|
|
39
|
+
* @returns {typeof settings[Key]}
|
|
40
|
+
*/
|
|
41
|
+
export function getSetting (key) {
|
|
42
|
+
return settings[key]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @template {keyof typeof settings} Key
|
|
47
|
+
* @param {Key} key
|
|
48
|
+
* @param {typeof settings[Key]} value
|
|
49
|
+
* @returns {void}
|
|
50
|
+
*/
|
|
51
|
+
export function updateSetting (key, value) {
|
|
52
|
+
settings[key] = value
|
|
53
|
+
fs.writeFileSync(
|
|
54
|
+
settingsPath,
|
|
55
|
+
Buffer.from(JSON.stringify(settings)).toString('base64')
|
|
56
|
+
)
|
|
57
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@socketsecurity/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "CLI tool for Socket.dev",
|
|
5
5
|
"homepage": "http://github.com/SocketDev/socket-cli-js",
|
|
6
6
|
"repository": {
|
|
@@ -43,11 +43,12 @@
|
|
|
43
43
|
"test": "run-s check test:*"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@socketsecurity/eslint-config": "^
|
|
46
|
+
"@socketsecurity/eslint-config": "^3.0.1",
|
|
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/
|
|
50
|
+
"@types/micromatch": "^4.0.2",
|
|
51
|
+
"@types/mocha": "^10.0.1",
|
|
51
52
|
"@types/mock-fs": "^4.13.1",
|
|
52
53
|
"@types/node": "^14.18.31",
|
|
53
54
|
"@types/npm": "^7.19.0",
|
|
@@ -84,7 +85,7 @@
|
|
|
84
85
|
"dependencies": {
|
|
85
86
|
"@apideck/better-ajv-errors": "^0.3.6",
|
|
86
87
|
"@socketsecurity/config": "^2.0.0",
|
|
87
|
-
"@socketsecurity/sdk": "^0.
|
|
88
|
+
"@socketsecurity/sdk": "^0.6.0",
|
|
88
89
|
"chalk": "^5.1.2",
|
|
89
90
|
"globby": "^13.1.3",
|
|
90
91
|
"hpagent": "^1.2.0",
|
|
@@ -93,6 +94,7 @@
|
|
|
93
94
|
"is-interactive": "^2.0.0",
|
|
94
95
|
"is-unicode-supported": "^1.3.0",
|
|
95
96
|
"meow": "^11.0.0",
|
|
97
|
+
"micromatch": "^4.0.5",
|
|
96
98
|
"ora": "^6.1.2",
|
|
97
99
|
"pony-cause": "^2.1.8",
|
|
98
100
|
"prompts": "^2.4.2",
|
package/lib/shadow/global.d.ts
DELETED