Skip to content

Commit b1d0cda

Browse files
authored
Merge branch 'master' into upgraded-cli-login
Signed-off-by: 101arrowz <[email protected]>
2 parents 57c17ac + fca96a3 commit b1d0cda

File tree

10 files changed

+2713
-5639
lines changed

10 files changed

+2713
-5639
lines changed

.npmrc

Lines changed: 0 additions & 1 deletion
This file was deleted.

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,10 @@ socket report view QXU8PmK7LfH608RAwfIKdbcHgwEd_ZeWJ9QEGv05FJUQ
2626

2727
* `socket report create <path(s)-to-folder-or-file>` - creates a report on [socket.dev](https://socket.dev/)
2828

29-
Uploads the specified `package.json` and lock files and, if any folder is specified, the ones found in there. Also includes the complementary `package.json` and lock file to any specified. Currently `package-lock.json` and `yarn.lock` are supported.
29+
Uploads the specified `package.json` and lock files for JavaScript and Python dependency manifests.
30+
If any folder is specified, the ones found in there recursively are uploaded.
3031

31-
Supports globbing such as `**/package.json`.
32+
Supports globbing such as `**/package.json`, `**/requirements.txt`, and `**/pyproject.toml`.
3233

3334
Ignores any file specified in your project's `.gitignore`, the `projectIgnorePaths` in your project's [`socket.yml`](https://docs.socket.dev/docs/socket-yml) and on top of that has a sensible set of [default ignores](https://www.npmjs.com/package/ignore-by-default)
3435

lib/commands/info/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ function setupCommand (name, description, argv, importMeta) {
126126
async function fetchPackageData (pkgName, pkgVersion, { includeAllIssues, strict }) {
127127
const socketSdk = await setupSdk(getDefaultKey() || FREE_API_KEY)
128128
const spinner = ora(`Looking up data for version ${pkgVersion} of ${pkgName}`).start()
129-
const result = await handleApiCall(socketSdk.getIssuesByNPMPackage(pkgName, pkgVersion), spinner, 'looking up package')
129+
const result = await handleApiCall(socketSdk.getIssuesByNPMPackage(pkgName, pkgVersion), 'looking up package')
130130

131131
if (result.success === false) {
132132
return handleUnsuccessfulApiResponse('getIssuesByNPMPackage', result, spinner)

lib/commands/report/create.js

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,10 @@ async function setupCommand (name, description, argv, importMeta) {
107107
Usage
108108
$ ${name} <paths-to-package-folders-and-files>
109109
110-
Uploads the specified "package.json" and lock files and, if any folder is
111-
specified, the ones found in there. Also includes the complementary
112-
"package.json" and lock file to any specified. Currently "package-lock.json"
113-
and "yarn.lock" are supported.
110+
Uploads the specified "package.json" and lock files for JavaScript and Python dependency manifests.
111+
If any folder is specified, the ones found in there recursively are uploaded.
114112
115-
Supports globbing such as "**/package.json".
113+
Supports globbing such as "**/package.json", "**/requirements.txt", and "**/pyproject.toml".
116114
117115
Ignores any file specified in your project's ".gitignore", your project's
118116
"socket.yml" file's "projectIgnorePaths" and also has a sensible set of
@@ -181,7 +179,17 @@ async function setupCommand (name, description, argv, importMeta) {
181179
}
182180
})
183181

184-
const packagePaths = await getPackageFiles(cwd, cli.input, config, debugLog)
182+
// TODO: setupSdk(getDefaultKey() || FREE_API_KEY)
183+
const socketSdk = await setupSdk()
184+
const supportedFiles = await socketSdk.getReportSupportedFiles()
185+
.then(res => {
186+
if (!res.success) handleUnsuccessfulApiResponse('getReportSupportedFiles', res, ora())
187+
return res.data
188+
}).catch(cause => {
189+
throw new ErrorWithCause('Failed getting supported files for report', { cause })
190+
})
191+
192+
const packagePaths = await getPackageFiles(cwd, cli.input, config, supportedFiles, debugLog)
185193

186194
return {
187195
config,
@@ -212,7 +220,7 @@ async function createReport (packagePaths, { config, cwd, debugLog, dryRun }) {
212220
const socketSdk = await setupSdk()
213221
const spinner = ora(`Creating report with ${packagePaths.length} package files`).start()
214222
const apiCall = socketSdk.createReportFromFilePaths(packagePaths, cwd, config?.issueRules)
215-
const result = await handleApiCall(apiCall, spinner, 'creating report')
223+
const result = await handleApiCall(apiCall, 'creating report')
216224

217225
if (result.success === false) {
218226
return handleUnsuccessfulApiResponse('createReport', result, spinner)

lib/commands/report/view.js

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import chalk from 'chalk'
44
import meow from 'meow'
55
import ora from 'ora'
6+
import { ErrorWithCause } from 'pony-cause'
67

78
import { outputFlags, validationFlags } from '../../flags/index.js'
89
import { handleApiCall, handleUnsuccessfulApiResponse } from '../../utils/api-helpers.js'
@@ -102,6 +103,8 @@ function setupCommand (name, description, argv, importMeta) {
102103
* @typedef {import('@socketsecurity/sdk').SocketSdkReturnType<'getReport'>["data"]} ReportData
103104
*/
104105

106+
const MAX_TIMEOUT_RETRY = 5
107+
105108
/**
106109
* @param {string} reportId
107110
* @param {Pick<CommandContext, 'includeAllIssues' | 'strict'>} context
@@ -111,8 +114,22 @@ export async function fetchReportData (reportId, { includeAllIssues, strict }) {
111114
// Do the API call
112115

113116
const socketSdk = await setupSdk()
114-
const spinner = ora(`Fetching report with ID ${reportId}`).start()
115-
const result = await handleApiCall(socketSdk.getReport(reportId), spinner, 'fetching report')
117+
const spinner = ora(`Fetching report with ID ${reportId} (this could take a while)`).start()
118+
/** @type {import('@socketsecurity/sdk').SocketSdkResultType<'getReport'> | undefined} */
119+
let result
120+
for (let retry = 1; !result; ++retry) {
121+
try {
122+
result = await handleApiCall(socketSdk.getReport(reportId), 'fetching report')
123+
} catch (err) {
124+
if (
125+
retry >= MAX_TIMEOUT_RETRY ||
126+
!(err instanceof ErrorWithCause) ||
127+
err.cause?.cause?.response?.statusCode !== 524
128+
) {
129+
throw err
130+
}
131+
}
132+
}
116133

117134
if (result.success === false) {
118135
return handleUnsuccessfulApiResponse('getReport', result, spinner)
@@ -147,9 +164,7 @@ export function formatReportDataOutput (data, { name, outputJson, outputMarkdown
147164
console.log(JSON.stringify(data, undefined, 2))
148165
} else {
149166
const format = new ChalkOrMarkdown(!!outputMarkdown)
150-
const url = `https://socket.dev/npm/reports/${encodeURIComponent(reportId)}`
151-
152-
console.log('\nDetailed info on socket.dev: ' + format.hyperlink(reportId, url, { fallbackToUrl: true }))
167+
console.log('\nDetailed info on socket.dev: ' + format.hyperlink(reportId, data.url, { fallbackToUrl: true }))
153168
if (!outputMarkdown) {
154169
console.log(chalk.dim('\nOr rerun', chalk.italic(name), 'using the', chalk.italic('--json'), 'flag to get full JSON output'))
155170
}

lib/utils/api-helpers.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { AuthError } from './errors.js'
88
* @param {T} _name
99
* @param {import('@socketsecurity/sdk').SocketSdkErrorType<T>} result
1010
* @param {import('ora').Ora} spinner
11-
* @returns {void}
11+
* @returns {never}
1212
*/
1313
export function handleUnsuccessfulApiResponse (_name, result, spinner) {
1414
const resultError = 'error' in result && result.error && typeof result.error === 'object' ? result.error : {}
@@ -25,18 +25,16 @@ export function handleUnsuccessfulApiResponse (_name, result, spinner) {
2525
/**
2626
* @template T
2727
* @param {Promise<T>} value
28-
* @param {import('ora').Ora} spinner
2928
* @param {string} description
3029
* @returns {Promise<T>}
3130
*/
32-
export async function handleApiCall (value, spinner, description) {
31+
export async function handleApiCall (value, description) {
3332
/** @type {T} */
3433
let result
3534

3635
try {
3736
result = await value
3837
} catch (cause) {
39-
spinner.fail()
4038
throw new ErrorWithCause(`Failed ${description}`, { cause })
4139
}
4240

lib/utils/path-resolve.js

Lines changed: 61 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -5,57 +5,55 @@ import { globby } from 'globby'
55
import ignore from 'ignore'
66
// @ts-ignore This package provides no types
77
import { directories } from 'ignore-by-default'
8+
import micromatch from 'micromatch'
89
import { ErrorWithCause } from 'pony-cause'
910

1011
import { InputError } from './errors.js'
1112
import { isErrnoException } from './type-helpers.js'
1213

13-
/** @type {readonly string[]} */
14-
const SUPPORTED_LOCKFILES = [
15-
'package-lock.json',
16-
'yarn.lock',
17-
]
18-
1914
/**
2015
* There are a lot of possible folders that we should not be looking in and "ignore-by-default" helps us with defining those
2116
*
2217
* @type {readonly string[]}
2318
*/
2419
const ignoreByDefault = directories()
2520

26-
/** @type {readonly string[]} */
27-
const GLOB_IGNORE = [
28-
...ignoreByDefault.map(item => '**/' + item)
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+
}
3032

3133
/**
3234
* Resolves package.json and lockfiles from (globbed) input paths, applying relevant ignores
3335
*
3436
* @param {string} cwd The working directory to use when resolving paths
3537
* @param {string[]} inputPaths A list of paths to folders, package.json files and/or recognized lockfiles. Supports globs.
3638
* @param {import('@socketsecurity/config').SocketYml|undefined} config
39+
* @param {import('@socketsecurity/sdk').SocketSdkReturnType<"getReportSupportedFiles">['data']} supportedFiles
3740
* @param {typeof console.error} debugLog
3841
* @returns {Promise<string[]>}
3942
* @throws {InputError}
4043
*/
41-
export async function getPackageFiles (cwd, inputPaths, config, debugLog) {
44+
export async function getPackageFiles (cwd, inputPaths, config, supportedFiles, debugLog) {
4245
debugLog(`Globbed resolving ${inputPaths.length} paths:`, inputPaths)
4346

4447
// TODO: Does not support `~/` paths
4548
const entries = await globby(inputPaths, {
46-
absolute: true,
49+
...BASE_GLOBBY_OPTS,
4750
cwd,
48-
expandDirectories: false,
49-
gitignore: true,
50-
ignore: [...GLOB_IGNORE],
51-
markDirectories: true,
52-
onlyFiles: false,
53-
unique: true,
51+
onlyFiles: false
5452
})
5553

5654
debugLog(`Globbed resolved ${inputPaths.length} paths to ${entries.length} paths:`, entries)
5755

58-
const packageFiles = await mapGlobResultToFiles(entries)
56+
const packageFiles = await mapGlobResultToFiles(entries, supportedFiles)
5957

6058
debugLog(`Mapped ${entries.length} entries to ${packageFiles.length} files:`, packageFiles)
6159

@@ -73,11 +71,14 @@ export async function getPackageFiles (cwd, inputPaths, config, debugLog) {
7371
* Takes paths to folders, package.json and/or recognized lock files and resolves them to package.json + lockfile pairs (where possible)
7472
*
7573
* @param {string[]} entries
74+
* @param {import('@socketsecurity/sdk').SocketSdkReturnType<"getReportSupportedFiles">['data']} supportedFiles
7675
* @returns {Promise<string[]>}
7776
* @throws {InputError}
7877
*/
79-
export async function mapGlobResultToFiles (entries) {
80-
const packageFiles = await Promise.all(entries.map(mapGlobEntryToFiles))
78+
export async function mapGlobResultToFiles (entries, supportedFiles) {
79+
const packageFiles = await Promise.all(
80+
entries.map(entry => mapGlobEntryToFiles(entry, supportedFiles))
81+
)
8182

8283
const uniquePackageFiles = [...new Set(packageFiles.flat())]
8384

@@ -88,46 +89,58 @@ export async function mapGlobResultToFiles (entries) {
8889
* Takes a single path to a folder, package.json or a recognized lock file and resolves to a package.json + lockfile pair (where possible)
8990
*
9091
* @param {string} entry
92+
* @param {import('@socketsecurity/sdk').SocketSdkReturnType<'getReportSupportedFiles'>['data']} supportedFiles
9193
* @returns {Promise<string[]>}
9294
* @throws {InputError}
9395
*/
94-
export async function mapGlobEntryToFiles (entry) {
96+
export async function mapGlobEntryToFiles (entry, supportedFiles) {
9597
/** @type {string|undefined} */
96-
let pkgFile
97-
/** @type {string|undefined} */
98-
let lockFile
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)
100110
if (entry.endsWith('/')) {
101111
// If the match is a folder and that folder contains a package.json file, then include it
102112
const filePath = path.resolve(entry, 'package.json')
103-
pkgFile = await fileExists(filePath) ? filePath : undefined
104-
} else if (path.basename(entry) === 'package.json') {
105-
// If the match is a package.json file, then include it
106-
pkgFile = entry
107-
} else if (SUPPORTED_LOCKFILES.includes(path.basename(entry))) {
108-
// If the match is a lock file, include both it and the corresponding package.json file
109-
lockFile = entry
110-
pkgFile = path.resolve(path.dirname(entry), 'package.json')
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+
}
111131
}
112132

113133
// If we will include a package.json file but don't already have a corresponding lockfile, then look for one
114-
if (!lockFile && pkgFile) {
115-
const pkgDir = path.dirname(pkgFile)
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)
125136

126-
if (pkgFile && lockFile) {
127-
return [pkgFile, lockFile]
137+
jsLockFiles = await globby(jsLockFilePatterns, {
138+
...BASE_GLOBBY_OPTS,
139+
cwd: pkgDir
140+
})
128141
}
129142

130-
return pkgFile ? [pkgFile] : []
143+
return [...jsLockFiles, ...pyFiles].concat(pkgJSFile ? [pkgJSFile] : [])
131144
}
132145

133146
/**

0 commit comments

Comments
 (0)