@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
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
const { PassThrough } = require('stream')
|
|
3
|
+
const { isErrnoException } = require('../utils/type-helpers.cjs')
|
|
4
|
+
const ipc_version = require('../../package.json').version
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {import('stream').Readable} Readable
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {import('stream').Writable} Writable
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* @param {import('chalk')['default']['level']} colorLevel
|
|
14
|
+
* @param {boolean} isInteractive
|
|
15
|
+
* @param {any} npmlog
|
|
16
|
+
* @returns {Promise<{ captureTTY<RET extends any>(mutexFn: (input: Readable | null, output?: Writable, colorLevel: import('chalk')['default']['level']) => Promise<RET>): Promise<RET> }>}
|
|
17
|
+
*/
|
|
18
|
+
module.exports = async function createTTYServer (colorLevel, isInteractive, npmlog) {
|
|
19
|
+
const TTY_IPC = process.env['SOCKET_SECURITY_TTY_IPC']
|
|
20
|
+
const net = require('net')
|
|
21
|
+
/**
|
|
22
|
+
* @type {import('readline')}
|
|
23
|
+
*/
|
|
24
|
+
let readline
|
|
25
|
+
const isSTDINInteractive = true || isInteractive
|
|
26
|
+
if (!isSTDINInteractive && TTY_IPC) {
|
|
27
|
+
return {
|
|
28
|
+
async captureTTY (mutexFn) {
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
const conn = net.createConnection({
|
|
31
|
+
path: TTY_IPC
|
|
32
|
+
}).on('error', reject)
|
|
33
|
+
let captured = false
|
|
34
|
+
/**
|
|
35
|
+
* @type {Array<Buffer>}
|
|
36
|
+
*/
|
|
37
|
+
const bufs = []
|
|
38
|
+
conn.on('data', function awaitCapture (chunk) {
|
|
39
|
+
bufs.push(chunk)
|
|
40
|
+
/**
|
|
41
|
+
* @type {Buffer | null}
|
|
42
|
+
*/
|
|
43
|
+
let lineBuff = Buffer.concat(bufs)
|
|
44
|
+
try {
|
|
45
|
+
if (!captured) {
|
|
46
|
+
const EOL = lineBuff.indexOf('\n'.charCodeAt(0))
|
|
47
|
+
if (EOL !== -1) {
|
|
48
|
+
conn.removeListener('data', awaitCapture)
|
|
49
|
+
conn.push(lineBuff.slice(EOL + 1))
|
|
50
|
+
const {
|
|
51
|
+
ipc_version: remote_ipc_version,
|
|
52
|
+
capabilities: { input: hasInput, output: hasOutput, colorLevel: ipcColorLevel }
|
|
53
|
+
} = JSON.parse(lineBuff.slice(0, EOL).toString('utf-8'))
|
|
54
|
+
lineBuff = null
|
|
55
|
+
captured = true
|
|
56
|
+
if (remote_ipc_version !== ipc_version) {
|
|
57
|
+
throw new Error('Mismatched STDIO tunnel IPC version, ensure you only have 1 version of socket CLI being called.')
|
|
58
|
+
}
|
|
59
|
+
const input = hasInput ? new PassThrough() : null
|
|
60
|
+
input?.pause()
|
|
61
|
+
if (input) conn.pipe(input)
|
|
62
|
+
const output = hasOutput ? new PassThrough() : null
|
|
63
|
+
output?.pipe(conn)
|
|
64
|
+
// make ora happy
|
|
65
|
+
// @ts-ignore
|
|
66
|
+
output.isTTY = true
|
|
67
|
+
// @ts-ignore
|
|
68
|
+
output.cursorTo = function cursorTo (x, y, callback) {
|
|
69
|
+
readline = readline || require('readline')
|
|
70
|
+
// @ts-ignore
|
|
71
|
+
readline.cursorTo(this, x, y, callback)
|
|
72
|
+
}
|
|
73
|
+
// @ts-ignore
|
|
74
|
+
output.clearLine = function clearLine (dir, callback) {
|
|
75
|
+
readline = readline || require('readline')
|
|
76
|
+
// @ts-ignore
|
|
77
|
+
readline.clearLine(this, dir, callback)
|
|
78
|
+
}
|
|
79
|
+
mutexFn(hasInput ? input : null, hasOutput ? /** @type {Writable} */(output) : undefined, ipcColorLevel)
|
|
80
|
+
.then(resolve, reject)
|
|
81
|
+
.finally(() => {
|
|
82
|
+
conn.unref()
|
|
83
|
+
conn.end()
|
|
84
|
+
input?.end()
|
|
85
|
+
output?.end()
|
|
86
|
+
// process.exit(13)
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
} catch (e) {
|
|
91
|
+
reject(e)
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* @type {Array<{resolve(): void}>}}
|
|
100
|
+
*/
|
|
101
|
+
const pendingCaptures = []
|
|
102
|
+
let captured = false
|
|
103
|
+
const sock = path.join(require('os').tmpdir(), `socket-security-tty-${process.pid}.sock`)
|
|
104
|
+
process.env['SOCKET_SECURITY_TTY_IPC'] = sock
|
|
105
|
+
try {
|
|
106
|
+
await require('fs/promises').unlink(sock)
|
|
107
|
+
} catch (e) {
|
|
108
|
+
if (isErrnoException(e) && e.code !== 'ENOENT') {
|
|
109
|
+
throw e
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const input = isSTDINInteractive ? process.stdin : null
|
|
113
|
+
const output = process.stderr
|
|
114
|
+
if (input) {
|
|
115
|
+
await new Promise((resolve, reject) => {
|
|
116
|
+
const server = net.createServer(async (conn) => {
|
|
117
|
+
if (captured) {
|
|
118
|
+
const captured = new Promise((resolve) => {
|
|
119
|
+
pendingCaptures.push({
|
|
120
|
+
resolve () {
|
|
121
|
+
resolve(undefined)
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
})
|
|
125
|
+
await captured
|
|
126
|
+
} else {
|
|
127
|
+
captured = true
|
|
128
|
+
}
|
|
129
|
+
const wasProgressEnabled = npmlog.progressEnabled
|
|
130
|
+
npmlog.pause()
|
|
131
|
+
if (wasProgressEnabled) {
|
|
132
|
+
npmlog.disableProgress()
|
|
133
|
+
}
|
|
134
|
+
conn.write(`${JSON.stringify({
|
|
135
|
+
ipc_version,
|
|
136
|
+
capabilities: {
|
|
137
|
+
input: Boolean(input),
|
|
138
|
+
output: true,
|
|
139
|
+
colorLevel
|
|
140
|
+
}
|
|
141
|
+
})}\n`)
|
|
142
|
+
conn.on('data', (data) => {
|
|
143
|
+
output.write(data)
|
|
144
|
+
})
|
|
145
|
+
conn.on('error', (e) => {
|
|
146
|
+
output.write(`there was an error prompting from a subshell (${e.message}), socket npm closing`)
|
|
147
|
+
process.exit(1)
|
|
148
|
+
})
|
|
149
|
+
input.on('data', (data) => {
|
|
150
|
+
conn.write(data)
|
|
151
|
+
})
|
|
152
|
+
input.on('end', () => {
|
|
153
|
+
conn.unref()
|
|
154
|
+
conn.end()
|
|
155
|
+
if (wasProgressEnabled) {
|
|
156
|
+
npmlog.enableProgress()
|
|
157
|
+
}
|
|
158
|
+
npmlog.resume()
|
|
159
|
+
nextCapture()
|
|
160
|
+
})
|
|
161
|
+
}).listen(sock, () => resolve(server)).on('error', (err) => {
|
|
162
|
+
reject(err)
|
|
163
|
+
}).unref()
|
|
164
|
+
process.on('exit', () => {
|
|
165
|
+
server.close()
|
|
166
|
+
try {
|
|
167
|
+
require('fs').unlinkSync(sock)
|
|
168
|
+
} catch (e) {
|
|
169
|
+
if (isErrnoException(e) && e.code !== 'ENOENT') {
|
|
170
|
+
throw e
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
})
|
|
174
|
+
resolve(server)
|
|
175
|
+
})
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
*
|
|
179
|
+
*/
|
|
180
|
+
function nextCapture () {
|
|
181
|
+
if (pendingCaptures.length > 0) {
|
|
182
|
+
const nextCapture = pendingCaptures.shift()
|
|
183
|
+
if (nextCapture) {
|
|
184
|
+
nextCapture.resolve()
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
captured = false
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
async captureTTY (mutexFn) {
|
|
192
|
+
if (captured) {
|
|
193
|
+
const captured = new Promise((resolve) => {
|
|
194
|
+
pendingCaptures.push({
|
|
195
|
+
resolve () {
|
|
196
|
+
resolve(undefined)
|
|
197
|
+
}
|
|
198
|
+
})
|
|
199
|
+
})
|
|
200
|
+
await captured
|
|
201
|
+
} else {
|
|
202
|
+
captured = true
|
|
203
|
+
}
|
|
204
|
+
const wasProgressEnabled = npmlog.progressEnabled
|
|
205
|
+
try {
|
|
206
|
+
npmlog.pause()
|
|
207
|
+
if (wasProgressEnabled) {
|
|
208
|
+
npmlog.disableProgress()
|
|
209
|
+
}
|
|
210
|
+
// need await here for proper finally timing
|
|
211
|
+
return await mutexFn(input, output, colorLevel)
|
|
212
|
+
} finally {
|
|
213
|
+
if (wasProgressEnabled) {
|
|
214
|
+
npmlog.enableProgress()
|
|
215
|
+
}
|
|
216
|
+
npmlog.resume()
|
|
217
|
+
nextCapture()
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
package/lib/utils/api-helpers.js
CHANGED
|
@@ -25,18 +25,16 @@ export function handleUnsuccessfulApiResponse (_name, result, spinner) {
|
|
|
25
25
|
/**
|
|
26
26
|
* @template T
|
|
27
27
|
* @param {Promise<T>} value
|
|
28
|
-
* @param {import('ora').Ora} spinner
|
|
29
28
|
* @param {string} description
|
|
30
29
|
* @returns {Promise<T>}
|
|
31
30
|
*/
|
|
32
|
-
export async function handleApiCall (value,
|
|
31
|
+
export async function handleApiCall (value, description) {
|
|
33
32
|
/** @type {T} */
|
|
34
33
|
let result
|
|
35
34
|
|
|
36
35
|
try {
|
|
37
36
|
result = await value
|
|
38
37
|
} catch (cause) {
|
|
39
|
-
spinner.fail()
|
|
40
38
|
throw new ErrorWithCause(`Failed ${description}`, { cause })
|
|
41
39
|
}
|
|
42
40
|
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
//#region UX Constants
|
|
2
|
+
/**
|
|
3
|
+
* @typedef {{block: boolean, display: boolean}} RuleActionUX
|
|
4
|
+
*/
|
|
5
|
+
const IGNORE_UX = {
|
|
6
|
+
block: false,
|
|
7
|
+
display: false
|
|
8
|
+
}
|
|
9
|
+
const WARN_UX = {
|
|
10
|
+
block: false,
|
|
11
|
+
display: true
|
|
12
|
+
}
|
|
13
|
+
const ERROR_UX = {
|
|
14
|
+
block: true,
|
|
15
|
+
display: true
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region utils
|
|
19
|
+
/**
|
|
20
|
+
* @typedef { NonNullable<NonNullable<NonNullable<(Awaited<ReturnType<import('@socketsecurity/sdk').SocketSdk['postSettings']>> & {success: true})['data']['entries'][number]['settings'][string]>['issueRules']>>[string] | boolean } NonNormalizedIssueRule
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* @typedef { (NonNullable<NonNullable<(Awaited<ReturnType<import('@socketsecurity/sdk').SocketSdk['postSettings']>> & {success: true})['data']['defaults']['issueRules']>[string]> & { action: string }) | boolean } NonNormalizedResolvedIssueRule
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Iterates over all entries with ordered issue rule for deferal
|
|
27
|
+
* Iterates over all issue rules and finds the first defined value that does not defer otherwise uses the defaultValue
|
|
28
|
+
* Takes the value and converts into a UX workflow
|
|
29
|
+
*
|
|
30
|
+
* @param {Iterable<Iterable<NonNormalizedIssueRule>>} entriesOrderedIssueRules
|
|
31
|
+
* @param {NonNormalizedResolvedIssueRule} defaultValue
|
|
32
|
+
* @returns {RuleActionUX}
|
|
33
|
+
*/
|
|
34
|
+
function resolveIssueRuleUX (entriesOrderedIssueRules, defaultValue) {
|
|
35
|
+
if (defaultValue === true || defaultValue == null) {
|
|
36
|
+
defaultValue = {
|
|
37
|
+
action: 'error'
|
|
38
|
+
}
|
|
39
|
+
} else if (defaultValue === false) {
|
|
40
|
+
defaultValue = {
|
|
41
|
+
action: 'ignore'
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
let block = false
|
|
45
|
+
let display = false
|
|
46
|
+
let needDefault = true
|
|
47
|
+
iterate_entries:
|
|
48
|
+
for (const issueRuleArr of entriesOrderedIssueRules) {
|
|
49
|
+
for (const rule of issueRuleArr) {
|
|
50
|
+
if (issueRuleValueDoesNotDefer(rule)) {
|
|
51
|
+
// there was a rule, even if a defer, don't narrow to the default
|
|
52
|
+
needDefault = false
|
|
53
|
+
const narrowingFilter = uxForDefinedNonDeferValue(rule)
|
|
54
|
+
block = block || narrowingFilter.block
|
|
55
|
+
display = display || narrowingFilter.display
|
|
56
|
+
continue iterate_entries
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// all rules defer, narrow
|
|
60
|
+
const narrowingFilter = uxForDefinedNonDeferValue(defaultValue)
|
|
61
|
+
block = block || narrowingFilter.block
|
|
62
|
+
display = display || narrowingFilter.display
|
|
63
|
+
}
|
|
64
|
+
if (needDefault) {
|
|
65
|
+
// no config set a
|
|
66
|
+
const narrowingFilter = uxForDefinedNonDeferValue(defaultValue)
|
|
67
|
+
block = block || narrowingFilter.block
|
|
68
|
+
display = display || narrowingFilter.display
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
block,
|
|
72
|
+
display
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Negative form because it is narrowing the type
|
|
78
|
+
*
|
|
79
|
+
* @type {(issueRuleValue: NonNormalizedIssueRule) => issueRuleValue is NonNormalizedResolvedIssueRule}
|
|
80
|
+
*/
|
|
81
|
+
function issueRuleValueDoesNotDefer (issueRule) {
|
|
82
|
+
if (issueRule === undefined) {
|
|
83
|
+
return false
|
|
84
|
+
} else if (typeof issueRule === 'object' && issueRule) {
|
|
85
|
+
const { action } = issueRule
|
|
86
|
+
if (action === undefined || action === 'defer') {
|
|
87
|
+
return false
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return true
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Handles booleans for backwards compatibility
|
|
95
|
+
*
|
|
96
|
+
* @param {NonNormalizedResolvedIssueRule} issueRuleValue
|
|
97
|
+
* @returns {RuleActionUX}
|
|
98
|
+
*/
|
|
99
|
+
function uxForDefinedNonDeferValue (issueRuleValue) {
|
|
100
|
+
if (typeof issueRuleValue === 'boolean') {
|
|
101
|
+
return issueRuleValue ? ERROR_UX : IGNORE_UX
|
|
102
|
+
}
|
|
103
|
+
const { action } = issueRuleValue
|
|
104
|
+
if (action === 'warn') {
|
|
105
|
+
return WARN_UX
|
|
106
|
+
} else if (action === 'ignore') {
|
|
107
|
+
return IGNORE_UX
|
|
108
|
+
}
|
|
109
|
+
return ERROR_UX
|
|
110
|
+
}
|
|
111
|
+
//#endregion
|
|
112
|
+
//#region exports
|
|
113
|
+
module.exports = {
|
|
114
|
+
/**
|
|
115
|
+
*
|
|
116
|
+
* @param {(Awaited<ReturnType<import('@socketsecurity/sdk').SocketSdk['postSettings']>> & {success: true})['data']} settings
|
|
117
|
+
* @returns {(context: {package: {name: string, version: string}, issue: {type: string}}) => RuleActionUX}
|
|
118
|
+
*/
|
|
119
|
+
createIssueUXLookup (settings) {
|
|
120
|
+
/**
|
|
121
|
+
* @type {Map<keyof (typeof settings.defaults.issueRules), RuleActionUX>}
|
|
122
|
+
*/
|
|
123
|
+
const cachedUX = new Map()
|
|
124
|
+
return (context) => {
|
|
125
|
+
const key = context.issue.type
|
|
126
|
+
/**
|
|
127
|
+
* @type {RuleActionUX | undefined}
|
|
128
|
+
*/
|
|
129
|
+
let ux = cachedUX.get(key)
|
|
130
|
+
if (ux) {
|
|
131
|
+
return ux
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* @type {Array<Array<NonNormalizedIssueRule>>}
|
|
135
|
+
*/
|
|
136
|
+
const entriesOrderedIssueRules = []
|
|
137
|
+
for (const settingsEntry of settings.entries) {
|
|
138
|
+
/**
|
|
139
|
+
* @type {Array<NonNormalizedIssueRule>}
|
|
140
|
+
*/
|
|
141
|
+
const orderedIssueRules = []
|
|
142
|
+
let target = settingsEntry.start
|
|
143
|
+
while (target !== null) {
|
|
144
|
+
const resolvedTarget = settingsEntry.settings[target]
|
|
145
|
+
if (!resolvedTarget) {
|
|
146
|
+
break
|
|
147
|
+
}
|
|
148
|
+
const issueRuleValue = resolvedTarget.issueRules?.[key]
|
|
149
|
+
if (typeof issueRuleValue !== 'undefined') {
|
|
150
|
+
orderedIssueRules.push(issueRuleValue)
|
|
151
|
+
}
|
|
152
|
+
target = resolvedTarget.deferTo ?? null
|
|
153
|
+
}
|
|
154
|
+
entriesOrderedIssueRules.push(orderedIssueRules)
|
|
155
|
+
}
|
|
156
|
+
const defaultValue = settings.defaults.issueRules[key]
|
|
157
|
+
/**
|
|
158
|
+
* @type {NonNormalizedResolvedIssueRule}
|
|
159
|
+
*/
|
|
160
|
+
let resolvedDefaultValue = {
|
|
161
|
+
action: 'error'
|
|
162
|
+
}
|
|
163
|
+
// @ts-ignore backcompat, cover with tests
|
|
164
|
+
if (defaultValue === false) {
|
|
165
|
+
resolvedDefaultValue = {
|
|
166
|
+
action: 'ignore'
|
|
167
|
+
}
|
|
168
|
+
// @ts-ignore backcompat, cover with tests
|
|
169
|
+
} else if (defaultValue && defaultValue !== true) {
|
|
170
|
+
resolvedDefaultValue = {
|
|
171
|
+
action: defaultValue.action ?? 'error'
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
ux = resolveIssueRuleUX(entriesOrderedIssueRules, resolvedDefaultValue)
|
|
175
|
+
cachedUX.set(key, ux)
|
|
176
|
+
return ux
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
//#endregion
|
package/lib/utils/misc.js
CHANGED
|
@@ -7,7 +7,7 @@ import { logSymbols } from './chalk-markdown.js'
|
|
|
7
7
|
export function createDebugLogger (printDebugLogs) {
|
|
8
8
|
return printDebugLogs
|
|
9
9
|
// eslint-disable-next-line no-console
|
|
10
|
-
? (...params) => console.error(logSymbols.info, ...params)
|
|
10
|
+
? /** @type { (...params: unknown[]) => void } */(...params) => console.error(logSymbols.info, ...params)
|
|
11
11
|
: () => {}
|
|
12
12
|
}
|
|
13
13
|
|
|
@@ -9,7 +9,7 @@ import micromatch from 'micromatch'
|
|
|
9
9
|
import { ErrorWithCause } from 'pony-cause'
|
|
10
10
|
|
|
11
11
|
import { InputError } from './errors.js'
|
|
12
|
-
import { isErrnoException } from './type-helpers.
|
|
12
|
+
import { isErrnoException } from './type-helpers.cjs'
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* There are a lot of possible folders that we should not be looking in and "ignore-by-default" helps us with defining those
|
|
@@ -100,17 +100,32 @@ export async function mapGlobEntryToFiles (entry, supportedFiles) {
|
|
|
100
100
|
let jsLockFiles = []
|
|
101
101
|
/** @type {string[]} */
|
|
102
102
|
let pyFiles = []
|
|
103
|
+
/** @type {string|undefined} */
|
|
104
|
+
let pkgGoFile
|
|
105
|
+
/** @type {string[]} */
|
|
106
|
+
let goExtraFiles = []
|
|
103
107
|
|
|
104
108
|
const jsSupported = supportedFiles['npm'] || {}
|
|
105
109
|
const jsLockFilePatterns = Object.keys(jsSupported)
|
|
106
110
|
.filter(key => key !== 'packagejson')
|
|
107
111
|
.map(key => /** @type {{ pattern: string }} */ (jsSupported[key]).pattern)
|
|
108
112
|
|
|
109
|
-
const pyFilePatterns = Object.values(supportedFiles['pypi'] || {})
|
|
113
|
+
const pyFilePatterns = Object.values(supportedFiles['pypi'] || {})
|
|
114
|
+
.map(p => /** @type {{ pattern: string }} */ (p).pattern)
|
|
115
|
+
|
|
116
|
+
const goSupported = supportedFiles['go'] || {}
|
|
117
|
+
const goSupplementalPatterns = Object.keys(goSupported)
|
|
118
|
+
.filter(key => key !== 'gomod')
|
|
119
|
+
.map(key => /** @type {{ pattern: string }} */ (goSupported[key]).pattern)
|
|
120
|
+
|
|
110
121
|
if (entry.endsWith('/')) {
|
|
111
122
|
// If the match is a folder and that folder contains a package.json file, then include it
|
|
112
|
-
const
|
|
113
|
-
if (await fileExists(
|
|
123
|
+
const jsPkg = path.resolve(entry, 'package.json')
|
|
124
|
+
if (await fileExists(jsPkg)) pkgJSFile = jsPkg
|
|
125
|
+
|
|
126
|
+
const goPkg = path.resolve(entry, 'go.mod')
|
|
127
|
+
if (await fileExists(goPkg)) pkgGoFile = goPkg
|
|
128
|
+
|
|
114
129
|
pyFiles = await globby(pyFilePatterns, {
|
|
115
130
|
...BASE_GLOBBY_OPTS,
|
|
116
131
|
cwd: entry
|
|
@@ -125,6 +140,11 @@ export async function mapGlobEntryToFiles (entry, supportedFiles) {
|
|
|
125
140
|
jsLockFiles = [entry]
|
|
126
141
|
pkgJSFile = path.resolve(path.dirname(entry), 'package.json')
|
|
127
142
|
if (!(await fileExists(pkgJSFile))) return []
|
|
143
|
+
} else if (entryFile === 'go.mod') {
|
|
144
|
+
pkgGoFile = entry
|
|
145
|
+
} else if (micromatch.isMatch(entryFile, goSupplementalPatterns)) {
|
|
146
|
+
goExtraFiles = [entry]
|
|
147
|
+
pkgGoFile = path.resolve(path.dirname(entry), 'go.mod')
|
|
128
148
|
} else if (micromatch.isMatch(entryFile, pyFilePatterns)) {
|
|
129
149
|
pyFiles = [entry]
|
|
130
150
|
}
|
|
@@ -140,7 +160,19 @@ export async function mapGlobEntryToFiles (entry, supportedFiles) {
|
|
|
140
160
|
})
|
|
141
161
|
}
|
|
142
162
|
|
|
143
|
-
|
|
163
|
+
if (!goExtraFiles.length && pkgGoFile) {
|
|
164
|
+
// get go.sum whenever possible
|
|
165
|
+
const pkgDir = path.dirname(pkgGoFile)
|
|
166
|
+
|
|
167
|
+
goExtraFiles = await globby(goSupplementalPatterns, {
|
|
168
|
+
...BASE_GLOBBY_OPTS,
|
|
169
|
+
cwd: pkgDir
|
|
170
|
+
})
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return [...jsLockFiles, ...pyFiles, ...goExtraFiles]
|
|
174
|
+
.concat(pkgJSFile ? [pkgJSFile] : [])
|
|
175
|
+
.concat(pkgGoFile ? [pkgGoFile] : [])
|
|
144
176
|
}
|
|
145
177
|
|
|
146
178
|
/**
|
package/lib/utils/sdk.js
CHANGED
|
@@ -9,25 +9,37 @@ import prompts from 'prompts'
|
|
|
9
9
|
import { AuthError } from './errors.js'
|
|
10
10
|
import { getSetting } from './settings.js'
|
|
11
11
|
|
|
12
|
+
export const FREE_API_KEY = 'sktsec_t_--RAN5U4ivauy4w37-6aoKyYPDt5ZbaT5JBVMqiwKo_api'
|
|
13
|
+
|
|
12
14
|
/**
|
|
13
15
|
* This API key should be stored globally for the duration of the CLI execution
|
|
14
16
|
*
|
|
15
17
|
* @type {string | undefined}
|
|
16
18
|
*/
|
|
17
|
-
let
|
|
19
|
+
let defaultKey
|
|
18
20
|
|
|
19
|
-
/** @returns {
|
|
20
|
-
export
|
|
21
|
-
|
|
21
|
+
/** @returns {string | undefined} */
|
|
22
|
+
export function getDefaultKey () {
|
|
23
|
+
defaultKey = getSetting('apiKey') || process.env['SOCKET_SECURITY_API_KEY'] || defaultKey
|
|
24
|
+
return defaultKey
|
|
25
|
+
}
|
|
22
26
|
|
|
23
|
-
|
|
27
|
+
/**
|
|
28
|
+
* @param {string} [apiKey]
|
|
29
|
+
* @returns {Promise<import('@socketsecurity/sdk').SocketSdk>}
|
|
30
|
+
*/
|
|
31
|
+
export async function setupSdk (apiKey = getDefaultKey()) {
|
|
32
|
+
if (apiKey == null && isInteractive()) {
|
|
33
|
+
/**
|
|
34
|
+
* @type {{ apiKey: string }}
|
|
35
|
+
*/
|
|
24
36
|
const input = await prompts({
|
|
25
37
|
type: 'password',
|
|
26
38
|
name: 'apiKey',
|
|
27
|
-
message: 'Enter your Socket.dev API key (not saved)',
|
|
39
|
+
message: 'Enter your Socket.dev API key (not saved, use socket login to persist)',
|
|
28
40
|
})
|
|
29
41
|
|
|
30
|
-
apiKey =
|
|
42
|
+
apiKey = defaultKey = input.apiKey
|
|
31
43
|
}
|
|
32
44
|
|
|
33
45
|
if (!apiKey) {
|
package/lib/utils/settings.js
CHANGED
|
@@ -19,7 +19,11 @@ if (!dataHome) {
|
|
|
19
19
|
|
|
20
20
|
const settingsPath = path.join(dataHome, 'socket', 'settings')
|
|
21
21
|
|
|
22
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {Record<string, boolean | {action: 'error' | 'warn' | 'ignore' | 'defer'}>} IssueRules
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/** @type {{apiKey?: string | null, enforcedOrgs?: string[] | null}} */
|
|
23
27
|
let settings = {}
|
|
24
28
|
|
|
25
29
|
if (fs.existsSync(settingsPath)) {
|
|
@@ -42,6 +46,8 @@ export function getSetting (key) {
|
|
|
42
46
|
return settings[key]
|
|
43
47
|
}
|
|
44
48
|
|
|
49
|
+
let pendingSave = false
|
|
50
|
+
|
|
45
51
|
/**
|
|
46
52
|
* @template {keyof typeof settings} Key
|
|
47
53
|
* @param {Key} key
|
|
@@ -50,8 +56,14 @@ export function getSetting (key) {
|
|
|
50
56
|
*/
|
|
51
57
|
export function updateSetting (key, value) {
|
|
52
58
|
settings[key] = value
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
59
|
+
if (!pendingSave) {
|
|
60
|
+
pendingSave = true
|
|
61
|
+
process.nextTick(() => {
|
|
62
|
+
pendingSave = false
|
|
63
|
+
fs.writeFileSync(
|
|
64
|
+
settingsPath,
|
|
65
|
+
Buffer.from(JSON.stringify(settings)).toString('base64')
|
|
66
|
+
)
|
|
67
|
+
})
|
|
68
|
+
}
|
|
57
69
|
}
|
|
@@ -6,6 +6,9 @@ import updateNotifier from 'update-notifier'
|
|
|
6
6
|
export function initUpdateNotifier () {
|
|
7
7
|
readFile(new URL('../../package.json', import.meta.url), 'utf8')
|
|
8
8
|
.then(rawPkg => {
|
|
9
|
+
/**
|
|
10
|
+
* @type {Exclude<Parameters<typeof updateNotifier>[0], undefined>['pkg']}
|
|
11
|
+
*/
|
|
9
12
|
const pkg = JSON.parse(rawPkg)
|
|
10
13
|
updateNotifier({ pkg }).notify()
|
|
11
14
|
})
|