@socketsecurity/cli 0.8.0 → 0.9.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.
@@ -20,10 +20,13 @@ export const info = {
20
20
  const name = parentName + ' info'
21
21
 
22
22
  const input = setupCommand(name, info.description, argv, importMeta)
23
- const packageData = input && await fetchPackageData(input.pkgName, input.pkgVersion, input)
24
-
25
- if (packageData) {
26
- formatPackageDataOutput(packageData, { name, ...input })
23
+ if (input) {
24
+ const spinnerText = input.pkgVersion === 'latest' ? `Looking up data for the latest version of ${input.pkgName}\n` : `Looking up data for version ${input.pkgVersion} of ${input.pkgName}\n`
25
+ const spinner = ora(spinnerText).start()
26
+ const packageData = await fetchPackageData(input.pkgName, input.pkgVersion, input, spinner)
27
+ if (packageData) {
28
+ formatPackageDataOutput(packageData, { name, ...input }, spinner)
29
+ }
27
30
  }
28
31
  }
29
32
  }
@@ -90,16 +93,8 @@ function setupCommand (name, description, argv, importMeta) {
90
93
 
91
94
  const versionSeparator = rawPkgName.lastIndexOf('@')
92
95
 
93
- if (versionSeparator < 1) {
94
- throw new InputError('Need to specify a full package identifier, like eg: webtorrent@1.0.0')
95
- }
96
-
97
- const pkgName = rawPkgName.slice(0, versionSeparator)
98
- const pkgVersion = rawPkgName.slice(versionSeparator + 1)
99
-
100
- if (!pkgVersion) {
101
- throw new InputError('Need to specify a version, like eg: webtorrent@1.0.0')
102
- }
96
+ const pkgName = versionSeparator < 1 ? rawPkgName : rawPkgName.slice(0, versionSeparator)
97
+ const pkgVersion = versionSeparator < 1 ? 'latest' : rawPkgName.slice(versionSeparator + 1)
103
98
 
104
99
  return {
105
100
  includeAllIssues,
@@ -115,53 +110,78 @@ function setupCommand (name, description, argv, importMeta) {
115
110
  * @typedef PackageData
116
111
  * @property {import('@socketsecurity/sdk').SocketSdkReturnType<'getIssuesByNPMPackage'>["data"]} data
117
112
  * @property {Record<import('../../utils/format-issues').SocketIssue['severity'], number>} severityCount
113
+ * @property {import('@socketsecurity/sdk').SocketSdkReturnType<'getScoreByNPMPackage'>["data"]} score
118
114
  */
119
115
 
120
116
  /**
121
117
  * @param {string} pkgName
122
118
  * @param {string} pkgVersion
123
- * @param {Pick<CommandContext, 'includeAllIssues' | 'strict'>} context
119
+ * @param {Pick<CommandContext, 'includeAllIssues'>} context
120
+ * @param {import('ora').Ora} spinner
124
121
  * @returns {Promise<void|PackageData>}
125
122
  */
126
- async function fetchPackageData (pkgName, pkgVersion, { includeAllIssues, strict }) {
123
+ async function fetchPackageData (pkgName, pkgVersion, { includeAllIssues }, spinner) {
127
124
  const socketSdk = await setupSdk(getDefaultKey() || FREE_API_KEY)
128
- const spinner = ora(`Looking up data for version ${pkgVersion} of ${pkgName}`).start()
129
125
  const result = await handleApiCall(socketSdk.getIssuesByNPMPackage(pkgName, pkgVersion), 'looking up package')
126
+ const scoreResult = await handleApiCall(socketSdk.getScoreByNPMPackage(pkgName, pkgVersion), 'looking up package score')
130
127
 
131
128
  if (result.success === false) {
132
129
  return handleUnsuccessfulApiResponse('getIssuesByNPMPackage', result, spinner)
133
130
  }
134
131
 
135
- // Conclude the status of the API call
132
+ if (scoreResult.success === false) {
133
+ return handleUnsuccessfulApiResponse('getScoreByNPMPackage', scoreResult, spinner)
134
+ }
136
135
 
136
+ // Conclude the status of the API call
137
137
  const severityCount = getSeverityCount(result.data, includeAllIssues ? undefined : 'high')
138
138
 
139
- if (objectSome(severityCount)) {
140
- const issueSummary = formatSeverityCount(severityCount)
141
- spinner[strict ? 'fail' : 'succeed'](`Package has these issues: ${issueSummary}`)
142
- } else {
143
- spinner.succeed('Package has no issues')
144
- }
145
-
146
139
  return {
147
140
  data: result.data,
148
141
  severityCount,
142
+ score: scoreResult.data
149
143
  }
150
144
  }
151
145
 
152
146
  /**
153
147
  * @param {PackageData} packageData
154
148
  * @param {{ name: string } & CommandContext} context
149
+ * @param {import('ora').Ora} spinner
155
150
  * @returns {void}
156
151
  */
157
- function formatPackageDataOutput ({ data, severityCount }, { name, outputJson, outputMarkdown, pkgName, pkgVersion, strict }) {
152
+ function formatPackageDataOutput ({ data, severityCount, score }, { name, outputJson, outputMarkdown, pkgName, pkgVersion, strict }, spinner) {
158
153
  if (outputJson) {
159
154
  console.log(JSON.stringify(data, undefined, 2))
160
155
  } else {
156
+ console.log('\nPackage report card:')
157
+ const scoreResult = {
158
+ 'Supply Chain Risk': Math.floor(score.supplyChainRisk.score * 100),
159
+ 'Maintenance': Math.floor(score.maintenance.score * 100),
160
+ 'Quality': Math.floor(score.quality.score * 100),
161
+ 'Vulnerabilities': Math.floor(score.vulnerability.score * 100),
162
+ 'License': Math.floor(score.license.score * 100)
163
+ }
164
+ Object.entries(scoreResult).map(score => console.log(`- ${score[0]}: ${formatScore(score[1])}`))
165
+
166
+ // Package issues list
167
+ if (objectSome(severityCount)) {
168
+ const issueSummary = formatSeverityCount(severityCount)
169
+ console.log('\n')
170
+ spinner[strict ? 'fail' : 'succeed'](`Package has these issues: ${issueSummary}`)
171
+ formatPackageIssuesDetails(data, outputMarkdown)
172
+ } else {
173
+ console.log('\n')
174
+ spinner.succeed('Package has no issues')
175
+ }
176
+
177
+ // Link to issues list
161
178
  const format = new ChalkOrMarkdown(!!outputMarkdown)
162
179
  const url = `https://socket.dev/npm/package/${pkgName}/overview/${pkgVersion}`
163
-
164
- console.log('\nDetailed info on socket.dev: ' + format.hyperlink(`${pkgName} v${pkgVersion}`, url, { fallbackToUrl: true }))
180
+ if (pkgVersion === 'latest') {
181
+ console.log('\nDetailed info on socket.dev: ' + format.hyperlink(`${pkgName}`, url, { fallbackToUrl: true }))
182
+ } else {
183
+ console.log('\nDetailed info on socket.dev: ' + format.hyperlink(`${pkgName} v${pkgVersion}`, url, { fallbackToUrl: true }))
184
+ }
165
185
  if (!outputMarkdown) {
166
186
  console.log(chalk.dim('\nOr rerun', chalk.italic(name), 'using the', chalk.italic('--json'), 'flag to get full JSON output'))
167
187
  }
@@ -171,3 +191,55 @@ async function fetchPackageData (pkgName, pkgVersion, { includeAllIssues, strict
171
191
  process.exit(1)
172
192
  }
173
193
  }
194
+
195
+ /**
196
+ * @param {import('@socketsecurity/sdk').SocketSdkReturnType<'getIssuesByNPMPackage'>["data"]} packageData
197
+ * @param {boolean} outputMarkdown
198
+ * @returns {void[]}
199
+ */
200
+ function formatPackageIssuesDetails (packageData, outputMarkdown) {
201
+ const issueDetails = packageData.filter(d => d.value?.severity === 'high' || d.value?.severity === 'critical')
202
+
203
+ const uniqueIssues = issueDetails.reduce((/** @type {{ [key: string]: {count: Number, label: string | undefined} }} */ acc, issue) => {
204
+ const { type } = issue
205
+ if (type) {
206
+ if (!acc[type]) {
207
+ acc[type] = {
208
+ label: issue.value?.label,
209
+ count: 1
210
+ }
211
+ } else {
212
+ // @ts-ignore
213
+ acc[type].count += 1
214
+ }
215
+ }
216
+ return acc
217
+ }, {})
218
+
219
+ const format = new ChalkOrMarkdown(!!outputMarkdown)
220
+ return Object.keys(uniqueIssues).map(issue => {
221
+ const issueWithLink = format.hyperlink(`${uniqueIssues[issue]?.label}`, `https://socket.dev/npm/issue/${issue}`, { fallbackToUrl: true })
222
+ if (uniqueIssues[issue]?.count === 1) {
223
+ return console.log(`- ${issueWithLink}`)
224
+ }
225
+ return console.log(`- ${issueWithLink}: ${uniqueIssues[issue]?.count}`)
226
+ })
227
+ }
228
+
229
+ /**
230
+ * @param {number} score
231
+ * @returns {string}
232
+ */
233
+ function formatScore (score) {
234
+ const error = chalk.hex('#de7c7b')
235
+ const warning = chalk.hex('#e59361')
236
+ const success = chalk.hex('#a4cb9d')
237
+
238
+ if (score > 80) {
239
+ return `${success(score)}`
240
+ } else if (score < 80 && score > 60) {
241
+ return `${warning(score)}`
242
+ } else {
243
+ return `${error(score)}`
244
+ }
245
+ }
@@ -5,6 +5,8 @@ import prompts from 'prompts'
5
5
  import terminalLink from 'terminal-link'
6
6
 
7
7
  import { AuthError, InputError } from '../../utils/errors.js'
8
+ import { prepareFlags } from '../../utils/flags.js'
9
+ import { printFlagList } from '../../utils/formatting.js'
8
10
  import { FREE_API_KEY, setupSdk } from '../../utils/sdk.js'
9
11
  import { getSetting, updateSetting } from '../../utils/settings.js'
10
12
 
@@ -14,6 +16,16 @@ const description = 'Socket API login'
14
16
  export const login = {
15
17
  description,
16
18
  run: async (argv, importMeta, { parentName }) => {
19
+ const flags = prepareFlags({
20
+ apiBaseUrl: {
21
+ type: 'string',
22
+ description: 'API server to connect to for login',
23
+ },
24
+ apiProxy: {
25
+ type: 'string',
26
+ description: 'Proxy to use when making connection to API server'
27
+ }
28
+ })
17
29
  const name = parentName + ' login'
18
30
  const cli = meow(`
19
31
  Usage
@@ -21,12 +33,19 @@ export const login = {
21
33
 
22
34
  Logs into the Socket API by prompting for an API key
23
35
 
36
+ Options
37
+ ${printFlagList({
38
+ 'api-base-url': flags.apiBaseUrl.description,
39
+ 'api-proxy': flags.apiProxy.description
40
+ }, 8)}
41
+
24
42
  Examples
25
43
  $ ${name}
26
44
  `, {
27
45
  argv,
28
46
  description,
29
47
  importMeta,
48
+ flags
30
49
  })
31
50
 
32
51
  /**
@@ -58,13 +77,27 @@ export const login = {
58
77
 
59
78
  const apiKey = result.apiKey || FREE_API_KEY
60
79
 
80
+ /**
81
+ * @type {string | null | undefined}
82
+ */
83
+ let apiBaseUrl = cli.flags.apiBaseUrl
84
+ apiBaseUrl ??= getSetting('apiBaseUrl') ??
85
+ undefined
86
+
87
+ /**
88
+ * @type {string | null | undefined}
89
+ */
90
+ let apiProxy = cli.flags.apiProxy
91
+ apiProxy ??= getSetting('apiProxy') ??
92
+ undefined
93
+
61
94
  const spinner = ora('Verifying API key...').start()
62
95
 
63
96
  /** @type {import('@socketsecurity/sdk').SocketSdkReturnType<'getOrganizations'>['data']} */
64
97
  let orgs
65
98
 
66
99
  try {
67
- const sdk = await setupSdk(apiKey)
100
+ const sdk = await setupSdk(apiKey, apiBaseUrl, apiProxy)
68
101
  const result = await sdk.getOrganizations()
69
102
  if (!result.success) throw new AuthError()
70
103
  orgs = result.data
@@ -131,6 +164,7 @@ export const login = {
131
164
  updateSetting('enforcedOrgs', enforcedOrgs)
132
165
  const oldKey = getSetting('apiKey')
133
166
  updateSetting('apiKey', apiKey)
167
+ updateSetting('apiBaseUrl', apiBaseUrl)
134
168
  spinner.succeed(`API credentials ${oldKey ? 'updated' : 'set'}`)
135
169
  }
136
170
  }
@@ -27,6 +27,8 @@ export const logout = {
27
27
  if (cli.input.length) cli.showHelp()
28
28
 
29
29
  updateSetting('apiKey', null)
30
+ updateSetting('apiBaseUrl', null)
31
+ updateSetting('apiProxy', null)
30
32
  updateSetting('enforcedOrgs', null)
31
33
  ora('Successfully logged out').succeed()
32
34
  }
@@ -179,7 +179,6 @@ async function setupCommand (name, description, argv, importMeta) {
179
179
  }
180
180
  })
181
181
 
182
- // TODO: setupSdk(getDefaultKey() || FREE_API_KEY)
183
182
  const socketSdk = await setupSdk()
184
183
  const supportedFiles = await socketSdk.getReportSupportedFiles()
185
184
  .then(res => {
@@ -40,34 +40,50 @@ try {
40
40
  */
41
41
 
42
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)
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)
56
66
  }
57
- }
58
- const result = await sdk.postSettings(orgs.map(org => {
59
67
  return {
60
- organization: org.id
68
+ orgs,
69
+ settings: result.data
61
70
  }
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
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
69
83
  }
70
84
  })
85
+ // mark apiKeySettingsInit as handled
86
+ apiKeySettingsInit.catch(() => {})
71
87
 
72
88
  /**
73
89
  *
@@ -78,42 +94,43 @@ async function findSocketYML () {
78
94
  const fs = require('fs/promises')
79
95
  while (dir !== prevDir) {
80
96
  const ymlPath = path.join(dir, 'socket.yml')
97
+ const yml = fs.readFile(ymlPath, 'utf-8')
81
98
  // mark as handled
82
- const yml = fs.readFile(ymlPath, 'utf-8').catch(() => {})
99
+ yml.catch(() => {})
83
100
  const yamlPath = path.join(dir, 'socket.yaml')
101
+ const yaml = fs.readFile(yamlPath, 'utf-8')
84
102
  // 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) {
103
+ yaml.catch(() => {})
104
+ /**
105
+ * @param {unknown} e
106
+ * @returns {boolean}
107
+ */
108
+ function checkFileFoundError (e) {
95
109
  if (isErrnoException(e)) {
96
110
  if (e.code !== 'ENOENT' && e.code !== 'EISDIR') {
97
111
  throw e
98
112
  }
99
- } else {
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)) {
100
124
  throw new Error('Found file but was unable to parse ' + ymlPath)
101
125
  }
102
126
  }
103
127
  try {
104
- const txt = await yaml
105
- if (txt != null) {
106
- return {
107
- path: yamlPath,
108
- parsed: config.parseSocketConfig(txt)
109
- }
128
+ return {
129
+ path: ymlPath,
130
+ parsed: config.parseSocketConfig(await yaml)
110
131
  }
111
132
  } catch (e) {
112
- if (isErrnoException(e)) {
113
- if (e.code !== 'ENOENT' && e.code !== 'EISDIR') {
114
- throw e
115
- }
116
- } else {
133
+ if (checkFileFoundError(e)) {
117
134
  throw new Error('Found file but was unable to parse ' + yamlPath)
118
135
  }
119
136
  }
@@ -124,11 +141,12 @@ async function findSocketYML () {
124
141
  }
125
142
 
126
143
  /**
127
- * @type {Promise<ReturnType<import('../utils/issue-rules.cjs')['createIssueUXLookup']>>}
144
+ * @type {Promise<ReturnType<import('../utils/issue-rules.cjs')['createIssueUXLookup']> | undefined>}
128
145
  */
129
- const uxLookupPromise = settingsPromise.then(async ({ getSetting }) => {
146
+ const uxLookupInit = settingsPromise.then(async ({ getSetting }) => {
130
147
  const enforcedOrgs = getSetting('enforcedOrgs') ?? []
131
- const { orgs, settings } = await apiKeySettingsPromise
148
+ const remoteSettings = await apiKeySettingsInit
149
+ const { orgs, settings } = remoteSettings
132
150
 
133
151
  // remove any organizations not being enforced
134
152
  for (const [i, org] of orgs.entries()) {
@@ -152,6 +170,8 @@ const uxLookupPromise = settingsPromise.then(async ({ getSetting }) => {
152
170
  }
153
171
  return createIssueUXLookup(settings)
154
172
  })
173
+ // mark uxLookupInit as handled
174
+ uxLookupInit.catch(() => {})
155
175
 
156
176
  // shadow `npm` and `npx` to mitigate subshells
157
177
  require('./link.cjs')(fs.realpathSync(path.join(__dirname, 'bin')), 'npm')
@@ -506,7 +526,7 @@ async function packagesHaveRiskyIssues (safeArb, _registry, pkgs, ora = null, _i
506
526
  const pkgDatas = []
507
527
  try {
508
528
  // TODO: determine org based on cwd, pass in
509
- const uxLookup = await uxLookupPromise
529
+ const uxLookup = await uxLookupInit
510
530
 
511
531
  for await (const pkgData of batchScan(pkgs.map(pkg => pkg.pkgid))) {
512
532
  /**
@@ -1,7 +1,8 @@
1
1
  const path = require('path')
2
2
  const { PassThrough } = require('stream')
3
- const { isErrnoException } = require('../utils/type-helpers.cjs')
3
+
4
4
  const ipc_version = require('../../package.json').version
5
+ const { isErrnoException } = require('../utils/type-helpers.cjs')
5
6
 
6
7
  /**
7
8
  * @typedef {import('stream').Readable} Readable
@@ -22,7 +23,7 @@ module.exports = async function createTTYServer (colorLevel, isInteractive, npml
22
23
  * @type {import('readline')}
23
24
  */
24
25
  let readline
25
- const isSTDINInteractive = true || isInteractive
26
+ const isSTDINInteractive = isInteractive
26
27
  if (!isSTDINInteractive && TTY_IPC) {
27
28
  return {
28
29
  async captureTTY (mutexFn) {
@@ -5,7 +5,6 @@ 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'
9
8
  import { ErrorWithCause } from 'pony-cause'
10
9
 
11
10
  import { InputError } from './errors.js'
@@ -94,85 +93,65 @@ export async function mapGlobResultToFiles (entries, supportedFiles) {
94
93
  * @throws {InputError}
95
94
  */
96
95
  export async function mapGlobEntryToFiles (entry, supportedFiles) {
97
- /** @type {string|undefined} */
98
- let pkgJSFile
99
- /** @type {string[]} */
100
- let jsLockFiles = []
101
- /** @type {string[]} */
102
- let pyFiles = []
103
- /** @type {string|undefined} */
104
- let pkgGoFile
105
- /** @type {string[]} */
106
- let goExtraFiles = []
107
-
108
96
  const jsSupported = supportedFiles['npm'] || {}
109
- const jsLockFilePatterns = Object.keys(jsSupported)
110
- .filter(key => key !== 'packagejson')
111
- .map(key => /** @type {{ pattern: string }} */ (jsSupported[key]).pattern)
97
+ const jsLockFilePatterns = Object.values(jsSupported)
98
+ // .filter(key => key !== 'packagejson')
99
+ .map(p => `**/${/** @type {{ pattern: string }} */ (p).pattern}`)
112
100
 
113
101
  const pyFilePatterns = Object.values(supportedFiles['pypi'] || {})
114
- .map(p => /** @type {{ pattern: string }} */ (p).pattern)
102
+ .map(p => `**/${/** @type {{ pattern: string }} */ (p).pattern}`)
115
103
 
116
104
  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
-
121
- if (entry.endsWith('/')) {
122
- // If the match is a folder and that folder contains a package.json file, then include it
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
-
129
- pyFiles = await globby(pyFilePatterns, {
130
- ...BASE_GLOBBY_OPTS,
131
- cwd: entry
132
- })
133
- } else {
134
- const entryFile = path.basename(entry)
135
-
136
- if (entryFile === 'package.json') {
137
- // If the match is a package.json file, then include it
138
- pkgJSFile = entry
139
- } else if (micromatch.isMatch(entryFile, jsLockFilePatterns)) {
140
- jsLockFiles = [entry]
141
- pkgJSFile = path.resolve(path.dirname(entry), 'package.json')
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')
148
- } else if (micromatch.isMatch(entryFile, pyFilePatterns)) {
149
- pyFiles = [entry]
150
- }
151
- }
152
-
153
- // If we will include a package.json file but don't already have a corresponding lockfile, then look for one
154
- if (!jsLockFiles.length && pkgJSFile) {
155
- const pkgDir = path.dirname(pkgJSFile)
156
-
157
- jsLockFiles = await globby(jsLockFilePatterns, {
158
- ...BASE_GLOBBY_OPTS,
159
- cwd: pkgDir
160
- })
161
- }
162
-
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] : [])
105
+ const goSupplementalPatterns = Object.values(goSupported)
106
+ // .filter(key => key !== 'gomod')
107
+ .map(p => `**/${/** @type {{ pattern: string }} */ (p).pattern}`)
108
+
109
+ const files = await globby([
110
+ ...jsLockFilePatterns,
111
+ ...pyFilePatterns,
112
+ ...goSupplementalPatterns
113
+ ], {
114
+ ...BASE_GLOBBY_OPTS,
115
+ onlyFiles: true,
116
+ cwd: path.resolve((await stat(entry)).isDirectory() ? entry : path.dirname(entry))
117
+ })
118
+ return files
119
+
120
+ // if (entry.endsWith('/')) {
121
+ // // If the match is a folder and that folder contains a package.json file, then include it
122
+ // const jsPkg = path.resolve(entry, 'package.json')
123
+ // if (await fileExists(jsPkg)) pkgJSFile = jsPkg
124
+
125
+ // const goPkg = path.resolve(entry, 'go.mod')
126
+ // if (await fileExists(goPkg)) pkgGoFile = goPkg
127
+
128
+ // pyFiles = await globby(pyFilePatterns, {
129
+ // ...BASE_GLOBBY_OPTS,
130
+ // cwd: entry
131
+ // })
132
+ // } else {
133
+ // const entryFile = path.basename(entry)
134
+
135
+ // if (entryFile === 'package.json') {
136
+ // // If the match is a package.json file, then include it
137
+ // pkgJSFile = entry
138
+ // } else if (micromatch.isMatch(entryFile, jsLockFilePatterns)) {
139
+ // jsLockFiles = [entry]
140
+ // pkgJSFile = path.resolve(path.dirname(entry), 'package.json')
141
+ // if (!(await fileExists(pkgJSFile))) return []
142
+ // } else if (entryFile === 'go.mod') {
143
+ // pkgGoFile = entry
144
+ // } else if (micromatch.isMatch(entryFile, goSupplementalPatterns)) {
145
+ // goExtraFiles = [entry]
146
+ // pkgGoFile = path.resolve(path.dirname(entry), 'go.mod')
147
+ // } else if (micromatch.isMatch(entryFile, pyFilePatterns)) {
148
+ // pyFiles = [entry]
149
+ // }
150
+ // }
151
+
152
+ // return [...jsLockFiles, ...pyFiles, ...goExtraFiles]
153
+ // .concat(pkgJSFile ? [pkgJSFile] : [])
154
+ // .concat(pkgGoFile ? [pkgGoFile] : [])
176
155
  }
177
156
 
178
157
  /**
package/lib/utils/sdk.js CHANGED
@@ -20,15 +20,47 @@ let defaultKey
20
20
 
21
21
  /** @returns {string | undefined} */
22
22
  export function getDefaultKey () {
23
- defaultKey = getSetting('apiKey') || process.env['SOCKET_SECURITY_API_KEY'] || defaultKey
23
+ defaultKey = process.env['SOCKET_SECURITY_API_KEY'] || getSetting('apiKey') || defaultKey
24
24
  return defaultKey
25
25
  }
26
26
 
27
+ /**
28
+ * The API server that should be used for operations
29
+ *
30
+ * @type {string | undefined}
31
+ */
32
+ let defaultAPIBaseUrl
33
+
34
+ /**
35
+ * @returns {string | undefined}
36
+ */
37
+ export function getDefaultAPIBaseUrl () {
38
+ defaultAPIBaseUrl = process.env['SOCKET_SECURITY_API_BASE_URL'] || getSetting('apiBaseUrl') || undefined
39
+ return defaultAPIBaseUrl
40
+ }
41
+
42
+ /**
43
+ * The API server that should be used for operations
44
+ *
45
+ * @type {string | undefined}
46
+ */
47
+ let defaultApiProxy
48
+
49
+ /**
50
+ * @returns {string | undefined}
51
+ */
52
+ export function getDefaultHTTPProxy () {
53
+ defaultApiProxy = process.env['SOCKET_SECURITY_API_PROXY'] || getSetting('apiProxy') || undefined
54
+ return defaultApiProxy
55
+ }
56
+
27
57
  /**
28
58
  * @param {string} [apiKey]
59
+ * @param {string} [apiBaseUrl]
60
+ * @param {string} [proxy]
29
61
  * @returns {Promise<import('@socketsecurity/sdk').SocketSdk>}
30
62
  */
31
- export async function setupSdk (apiKey = getDefaultKey()) {
63
+ export async function setupSdk (apiKey = getDefaultKey(), apiBaseUrl = getDefaultAPIBaseUrl(), proxy = getDefaultHTTPProxy()) {
32
64
  if (apiKey == null && isInteractive()) {
33
65
  /**
34
66
  * @type {{ apiKey: string }}
@@ -49,11 +81,11 @@ export async function setupSdk (apiKey = getDefaultKey()) {
49
81
  /** @type {import('@socketsecurity/sdk').SocketSdkOptions["agent"]} */
50
82
  let agent
51
83
 
52
- if (process.env['SOCKET_SECURITY_API_PROXY']) {
84
+ if (proxy) {
53
85
  const { HttpProxyAgent, HttpsProxyAgent } = await import('hpagent')
54
86
  agent = {
55
- http: new HttpProxyAgent({ proxy: process.env['SOCKET_SECURITY_API_PROXY'] }),
56
- https: new HttpsProxyAgent({ proxy: process.env['SOCKET_SECURITY_API_PROXY'] }),
87
+ http: new HttpProxyAgent({ proxy }),
88
+ https: new HttpsProxyAgent({ proxy }),
57
89
  }
58
90
  }
59
91
  const packageJsonPath = join(dirname(fileURLToPath(import.meta.url)), '../../package.json')
@@ -62,7 +94,7 @@ export async function setupSdk (apiKey = getDefaultKey()) {
62
94
  /** @type {import('@socketsecurity/sdk').SocketSdkOptions} */
63
95
  const sdkOptions = {
64
96
  agent,
65
- baseUrl: process.env['SOCKET_SECURITY_API_BASE_URL'],
97
+ baseUrl: apiBaseUrl,
66
98
  userAgent: createUserAgentFromPkgJson(JSON.parse(packageJson))
67
99
  }
68
100
 
@@ -23,7 +23,7 @@ const settingsPath = path.join(dataHome, 'socket', 'settings')
23
23
  * @typedef {Record<string, boolean | {action: 'error' | 'warn' | 'ignore' | 'defer'}>} IssueRules
24
24
  */
25
25
 
26
- /** @type {{apiKey?: string | null, enforcedOrgs?: string[] | null}} */
26
+ /** @type {{apiKey?: string | null, enforcedOrgs?: string[] | null, apiBaseUrl?: string | null, apiProxy?: string | null}} */
27
27
  let settings = {}
28
28
 
29
29
  if (fs.existsSync(settingsPath)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@socketsecurity/cli",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "CLI tool for Socket.dev",
5
5
  "homepage": "http://github.com/SocketDev/socket-cli-js",
6
6
  "repository": {
@@ -82,7 +82,7 @@
82
82
  "dependencies": {
83
83
  "@apideck/better-ajv-errors": "^0.3.6",
84
84
  "@socketsecurity/config": "^2.0.0",
85
- "@socketsecurity/sdk": "^0.7.2",
85
+ "@socketsecurity/sdk": "^0.7.3",
86
86
  "chalk": "^5.1.2",
87
87
  "globby": "^13.1.3",
88
88
  "hpagent": "^1.2.0",
@@ -91,7 +91,6 @@
91
91
  "is-interactive": "^2.0.0",
92
92
  "is-unicode-supported": "^1.3.0",
93
93
  "meow": "^12.0.1",
94
- "micromatch": "^4.0.5",
95
94
  "ora": "^6.1.2",
96
95
  "pony-cause": "^2.1.8",
97
96
  "prompts": "^2.4.2",