Skip to content

Commit 21de2dd

Browse files
authored
Merge pull request #47 from SocketDev/python-cli
Python CLI support
2 parents f072456 + 4ce9827 commit 21de2dd

File tree

5 files changed

+189
-99
lines changed

5 files changed

+189
-99
lines changed

lib/commands/report/create.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,17 @@ async function setupCommand (name, description, argv, importMeta) {
181181
}
182182
})
183183

184-
const packagePaths = await getPackageFiles(cwd, cli.input, config, debugLog)
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)
185195

186196
return {
187197
config,

lib/utils/api-helpers.js

Lines changed: 1 addition & 1 deletion
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 : {}

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
/**

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"@tsconfig/node14": "^1.0.3",
4848
"@types/chai": "^4.3.3",
4949
"@types/chai-as-promised": "^7.1.5",
50+
"@types/micromatch": "^4.0.2",
5051
"@types/mocha": "^10.0.1",
5152
"@types/mock-fs": "^4.13.1",
5253
"@types/node": "^14.18.31",
@@ -84,7 +85,7 @@
8485
"dependencies": {
8586
"@apideck/better-ajv-errors": "^0.3.6",
8687
"@socketsecurity/config": "^2.0.0",
87-
"@socketsecurity/sdk": "^0.5.4",
88+
"@socketsecurity/sdk": "^0.6.0",
8889
"chalk": "^5.1.2",
8990
"globby": "^13.1.3",
9091
"hpagent": "^1.2.0",
@@ -93,6 +94,7 @@
9394
"is-interactive": "^2.0.0",
9495
"is-unicode-supported": "^1.3.0",
9596
"meow": "^11.0.0",
97+
"micromatch": "^4.0.5",
9698
"ora": "^6.1.2",
9799
"pony-cause": "^2.1.8",
98100
"prompts": "^2.4.2",

0 commit comments

Comments
 (0)