@socketsecurity/cli
Advanced tools
Comparing version 0.1.2 to 0.2.0
@@ -6,16 +6,35 @@ /* eslint-disable no-console */ | ||
import ora from 'ora' | ||
import { ErrorWithCause } from 'pony-cause' | ||
import { handleApiCall, handleUnsuccessfulApiResponse } from '../../utils/api-helpers.js' | ||
import { ChalkOrMarkdown } from '../../utils/chalk-markdown.js' | ||
import { AuthError, InputError } from '../../utils/errors.js' | ||
import { InputError } from '../../utils/errors.js' | ||
import { getSeveritySummary } from '../../utils/format-issues.js' | ||
import { printFlagList } from '../../utils/formatting.js' | ||
import { stringJoinWithSeparateFinalSeparator } from '../../utils/misc.js' | ||
import { setupSdk } from '../../utils/sdk.js' | ||
const description = 'Look up info regarding a package' | ||
/** @type {import('../../utils/meow-with-subcommands').CliSubcommand} */ | ||
export const info = { | ||
description: 'Look up info regarding a package', | ||
async run (argv, importMeta, { parentName }) { | ||
const name = parentName + ' info' | ||
/** @type {import('../../utils/meow-with-subcommands').CliSubcommandRun} */ | ||
const run = async (argv, importMeta, { parentName }) => { | ||
const name = parentName + ' info' | ||
const input = setupCommand(name, info.description, argv, importMeta) | ||
const result = input && await fetchPackageData(input.pkgName, input.pkgVersion) | ||
if (result) { | ||
formatPackageDataOutput(result.data, { name, ...input }) | ||
} | ||
} | ||
} | ||
// Internal functions | ||
/** | ||
* @param {string} name | ||
* @param {string} description | ||
* @param {readonly string[]} argv | ||
* @param {ImportMeta} importMeta | ||
* @returns {void|{ outputJson: boolean, outputMarkdown: boolean, pkgName: string, pkgVersion: string }} | ||
*/ | ||
function setupCommand (name, description, argv, importMeta) { | ||
const cli = meow(` | ||
@@ -27,3 +46,2 @@ Usage | ||
${printFlagList({ | ||
'--debug': 'Output debug information', | ||
'--json': 'Output result as json', | ||
@@ -41,7 +59,2 @@ '--markdown': 'Output result as markdown', | ||
flags: { | ||
debug: { | ||
type: 'boolean', | ||
alias: 'd', | ||
default: false, | ||
}, | ||
json: { | ||
@@ -89,51 +102,40 @@ type: 'boolean', | ||
return { | ||
outputJson, | ||
outputMarkdown, | ||
pkgName, | ||
pkgVersion | ||
} | ||
} | ||
/** | ||
* @param {string} pkgName | ||
* @param {string} pkgVersion | ||
* @returns {Promise<void|import('@socketsecurity/sdk').SocketSdkReturnType<'getIssuesByNPMPackage'>>} | ||
*/ | ||
async function fetchPackageData (pkgName, pkgVersion) { | ||
const socketSdk = await setupSdk() | ||
const spinner = ora(`Looking up data for version ${pkgVersion} of ${pkgName}`).start() | ||
const result = await handleApiCall(socketSdk.getIssuesByNPMPackage(pkgName, pkgVersion), spinner, 'looking up package') | ||
/** @type {Awaited<ReturnType<import('@socketsecurity/sdk').SocketSdk["getIssuesByNPMPackage"]>>} */ | ||
let result | ||
try { | ||
result = await socketSdk.getIssuesByNPMPackage(pkgName, pkgVersion) | ||
} catch (cause) { | ||
spinner.fail() | ||
throw new ErrorWithCause('Failed to look up package', { cause }) | ||
} | ||
if (result.success === false) { | ||
if (result.status === 401 || result.status === 403) { | ||
spinner.stop() | ||
throw new AuthError(result.error.message) | ||
} | ||
spinner.fail(chalk.white.bgRed('API returned an error:') + ' ' + result.error.message) | ||
process.exit(1) | ||
return handleUnsuccessfulApiResponse(result, spinner) | ||
} | ||
const data = result.data | ||
// Conclude the status of the API call | ||
/** @typedef {(typeof data)[number]["value"] extends infer U | undefined ? U : never} SocketSdkIssue */ | ||
/** @type {Record<SocketSdkIssue["severity"], number>} */ | ||
const severityCount = { low: 0, middle: 0, high: 0, critical: 0 } | ||
for (const issue of data) { | ||
const value = issue.value | ||
const issueSummary = getSeveritySummary(result.data) | ||
spinner.succeed(`Found ${issueSummary || 'no'} issues for version ${pkgVersion} of ${pkgName}`) | ||
if (!value) { | ||
continue | ||
} | ||
return result | ||
} | ||
if (severityCount[value.severity] !== undefined) { | ||
severityCount[value.severity] += 1 | ||
} | ||
} | ||
/** | ||
* @param {import('@socketsecurity/sdk').SocketSdkReturnType<'getIssuesByNPMPackage'>["data"]} data | ||
* @param {{ name: string, outputJson: boolean, outputMarkdown: boolean, pkgName: string, pkgVersion: string }} context | ||
* @returns {void} | ||
*/ | ||
function formatPackageDataOutput (data, { name, outputJson, outputMarkdown, pkgName, pkgVersion }) { | ||
// If JSON, output and return... | ||
const issueSummary = stringJoinWithSeparateFinalSeparator([ | ||
severityCount.critical ? severityCount.critical + ' critical' : undefined, | ||
severityCount.high ? severityCount.high + ' high' : undefined, | ||
severityCount.middle ? severityCount.middle + ' middle' : undefined, | ||
severityCount.low ? severityCount.low + ' low' : undefined, | ||
]) | ||
spinner.succeed(`Found ${issueSummary || 'no'} issues for version ${pkgVersion} of ${pkgName}`) | ||
if (outputJson) { | ||
@@ -144,2 +146,4 @@ console.log(JSON.stringify(data, undefined, 2)) | ||
// ...else do the CLI / Markdown output dance | ||
const format = new ChalkOrMarkdown(!!outputMarkdown) | ||
@@ -149,3 +153,2 @@ const url = `https://socket.dev/npm/package/${pkgName}/overview/${pkgVersion}` | ||
console.log('\nDetailed info on socket.dev: ' + format.hyperlink(`${pkgName} v${pkgVersion}`, url, { fallbackToUrl: true })) | ||
if (!outputMarkdown) { | ||
@@ -155,4 +158,1 @@ console.log(chalk.dim('\nOr rerun', chalk.italic(name), 'using the', chalk.italic('--json'), 'flag to get full JSON output')) | ||
} | ||
/** @type {import('../../utils/meow-with-subcommands').CliSubcommand} */ | ||
export const info = { description, run } |
@@ -6,3 +6,2 @@ /* eslint-disable no-console */ | ||
import chalk from 'chalk' | ||
import meow from 'meow' | ||
@@ -12,4 +11,5 @@ import ora from 'ora' | ||
import { handleApiCall, handleUnsuccessfulApiResponse } from '../../utils/api-helpers.js' | ||
import { ChalkOrMarkdown, logSymbols } from '../../utils/chalk-markdown.js' | ||
import { AuthError, InputError } from '../../utils/errors.js' | ||
import { InputError } from '../../utils/errors.js' | ||
import { printFlagList } from '../../utils/formatting.js' | ||
@@ -19,9 +19,49 @@ import { createDebugLogger } from '../../utils/misc.js' | ||
import { isErrnoException } from '../../utils/type-helpers.js' | ||
import { fetchReportData, formatReportDataOutput } from './view.js' | ||
const description = 'Create a project report' | ||
/** @type {import('../../utils/meow-with-subcommands').CliSubcommand} */ | ||
export const create = { | ||
description: 'Create a project report', | ||
async run (argv, importMeta, { parentName }) { | ||
const name = parentName + ' create' | ||
/** @type {import('../../utils/meow-with-subcommands').CliSubcommandRun} */ | ||
const run = async (argv, importMeta, { parentName }) => { | ||
const name = parentName + ' create' | ||
const input = await setupCommand(name, create.description, argv, importMeta) | ||
if (input) { | ||
const { | ||
cwd, | ||
debugLog, | ||
dryRun, | ||
outputJson, | ||
outputMarkdown, | ||
packagePaths, | ||
view, | ||
} = input | ||
const result = input && await createReport(packagePaths, { cwd, debugLog, dryRun }) | ||
if (result && view) { | ||
const reportId = result.data.id | ||
const reportResult = input && await fetchReportData(reportId) | ||
if (reportResult) { | ||
formatReportDataOutput(reportResult.data, { name, outputJson, outputMarkdown, reportId }) | ||
} | ||
} else if (result) { | ||
formatReportCreationOutput(result.data, { outputJson, outputMarkdown }) | ||
} | ||
} | ||
} | ||
} | ||
// Internal functions | ||
/** | ||
* @param {string} name | ||
* @param {string} description | ||
* @param {readonly string[]} argv | ||
* @param {ImportMeta} importMeta | ||
* @returns {Promise<void|{ cwd: string, debugLog: typeof console.error, dryRun: boolean, outputJson: boolean, outputMarkdown: boolean, packagePaths: string[], view: boolean }>} | ||
*/ | ||
async function setupCommand (name, description, argv, importMeta) { | ||
const cli = meow(` | ||
@@ -37,2 +77,3 @@ Usage | ||
'--markdown': 'Output result as markdown', | ||
'--view': 'Will wait for and return the created report' | ||
}, 6)} | ||
@@ -44,2 +85,3 @@ | ||
$ ${name} /path/to/a/package.json /path/to/another/package.json | ||
$ ${name} . --view --json | ||
`, { | ||
@@ -69,2 +111,7 @@ argv, | ||
}, | ||
view: { | ||
type: 'boolean', | ||
alias: 'v', | ||
default: false, | ||
}, | ||
} | ||
@@ -77,2 +124,3 @@ }) | ||
markdown: outputMarkdown, | ||
view, | ||
} = cli.flags | ||
@@ -90,2 +138,19 @@ | ||
return { | ||
cwd, | ||
debugLog, | ||
dryRun, | ||
outputJson, | ||
outputMarkdown, | ||
packagePaths, | ||
view, | ||
} | ||
} | ||
/** | ||
* @param {string[]} packagePaths | ||
* @param {{ cwd: string, debugLog: typeof console.error, dryRun: boolean }} context | ||
* @returns {Promise<void|import('@socketsecurity/sdk').SocketSdkReturnType<'createReport'>>} | ||
*/ | ||
async function createReport (packagePaths, { cwd, debugLog, dryRun }) { | ||
debugLog(`${logSymbols.info} Uploading:`, packagePaths.join(`\n${logSymbols.info} Uploading:`)) | ||
@@ -98,28 +163,24 @@ | ||
const socketSdk = await setupSdk() | ||
const spinner = ora(`Creating report with ${packagePaths.length} package files`).start() | ||
const result = await handleApiCall(socketSdk.createReportFromFilePaths(packagePaths, cwd), spinner, 'creating report') | ||
/** @type {Awaited<ReturnType<typeof socketSdk.createReportFromFilePaths>>} */ | ||
let result | ||
try { | ||
result = await socketSdk.createReportFromFilePaths(packagePaths, cwd) | ||
} catch (cause) { | ||
spinner.fail() | ||
throw new ErrorWithCause('Failed creating report', { cause }) | ||
} | ||
if (result.success === false) { | ||
if (result.status === 401 || result.status === 403) { | ||
spinner.stop() | ||
throw new AuthError(result.error.message) | ||
} | ||
spinner.fail(chalk.white.bgRed('API returned an error:') + ' ' + result.error.message) | ||
process.exit(1) | ||
return handleUnsuccessfulApiResponse(result, spinner) | ||
} | ||
// Conclude the status of the API call | ||
spinner.succeed() | ||
return result | ||
} | ||
/** | ||
* @param {import('@socketsecurity/sdk').SocketSdkReturnType<'createReport'>["data"]} data | ||
* @param {{ outputJson: boolean, outputMarkdown: boolean }} context | ||
* @returns {void} | ||
*/ | ||
function formatReportCreationOutput (data, { outputJson, outputMarkdown }) { | ||
if (outputJson) { | ||
console.log(JSON.stringify(result.data, undefined, 2)) | ||
console.log(JSON.stringify(data, undefined, 2)) | ||
return | ||
@@ -130,8 +191,5 @@ } | ||
console.log('\nNew report: ' + format.hyperlink(result.data.id, result.data.url, { fallbackToUrl: true })) | ||
console.log('\nNew report: ' + format.hyperlink(data.id, data.url, { fallbackToUrl: true })) | ||
} | ||
/** @type {import('../../utils/meow-with-subcommands').CliSubcommand} */ | ||
export const create = { description, run } | ||
// TODO: Add globbing support with support for ignoring, as a "./**/package.json" in a project also traverses eg. node_modules | ||
@@ -138,0 +196,0 @@ /** |
import { meowWithSubcommands } from '../../utils/meow-with-subcommands.js' | ||
import { create } from './create.js' | ||
import { view } from './view.js' | ||
@@ -13,2 +14,3 @@ const description = 'Project report related commands' | ||
create, | ||
view, | ||
}, | ||
@@ -15,0 +17,0 @@ { |
{ | ||
"name": "@socketsecurity/cli", | ||
"version": "0.1.2", | ||
"version": "0.2.0", | ||
"description": "CLI tool for Socket.dev", | ||
@@ -48,26 +48,26 @@ "homepage": "http://github.com/SocketDev/socket-cli-js", | ||
"@types/prompts": "^2.4.1", | ||
"@typescript-eslint/eslint-plugin": "^5.36.2", | ||
"@typescript-eslint/parser": "^5.36.2", | ||
"@typescript-eslint/eslint-plugin": "^5.44.0", | ||
"@typescript-eslint/parser": "^5.44.0", | ||
"c8": "^7.12.0", | ||
"chai": "^4.3.6", | ||
"dependency-check": "^5.0.0-7", | ||
"eslint": "^8.23.0", | ||
"eslint": "^8.28.0", | ||
"eslint-config-standard": "^17.0.0", | ||
"eslint-config-standard-jsx": "^11.0.0", | ||
"eslint-import-resolver-typescript": "^3.5.1", | ||
"eslint-import-resolver-typescript": "^3.5.2", | ||
"eslint-plugin-import": "^2.26.0", | ||
"eslint-plugin-jsdoc": "^39.5.0", | ||
"eslint-plugin-n": "^15.3.0", | ||
"eslint-plugin-promise": "^6.0.1", | ||
"eslint-plugin-react": "^7.31.9", | ||
"eslint-plugin-n": "^15.5.1", | ||
"eslint-plugin-promise": "^6.1.1", | ||
"eslint-plugin-react": "^7.31.11", | ||
"eslint-plugin-react-hooks": "^4.6.0", | ||
"husky": "^8.0.1", | ||
"installed-check": "^6.0.4", | ||
"installed-check": "^6.0.5", | ||
"mocha": "^10.0.0", | ||
"npm-run-all2": "^6.0.2", | ||
"type-coverage": "^2.21.2", | ||
"typescript": "~4.8.4" | ||
"type-coverage": "^2.24.1", | ||
"typescript": "~4.9.3" | ||
}, | ||
"dependencies": { | ||
"@socketsecurity/sdk": "^0.3.1", | ||
"@socketsecurity/sdk": "^0.4.0", | ||
"chalk": "^5.1.2", | ||
@@ -79,3 +79,3 @@ "hpagent": "^1.2.0", | ||
"ora": "^6.1.2", | ||
"pony-cause": "^2.1.4", | ||
"pony-cause": "^2.1.8", | ||
"prompts": "^2.4.2", | ||
@@ -82,0 +82,0 @@ "terminal-link": "^3.0.0" |
@@ -18,3 +18,4 @@ # Socket CLI | ||
socket info webtorrent@1.9.1 | ||
socket report create package.json | ||
socket report create package.json --view | ||
socket report view QXU8PmK7LfH608RAwfIKdbcHgwEd_ZeWJ9QEGv05FJUQ | ||
``` | ||
@@ -25,9 +26,10 @@ | ||
* `socket info <package@version>` - looks up issues for a package | ||
* `socket report create` - uploads the specified `package.json` and/or `package-lock.json` to create a report on [socket.dev](https://socket.dev/). If only one of a `package.json`/`package-lock.json` has been specified, the other will be automatically found and uploaded if it exists | ||
* `socket report create <path(s)-to-folder-or-file>` - uploads the specified `package.json` and/or `package-lock.json` to create a report on [socket.dev](https://socket.dev/). If only one of a `package.json`/`package-lock.json` has been specified, the other will be automatically found and uploaded if it exists | ||
* `socket report view <report-id>` - looks up issues and scores from a report | ||
## Flags | ||
### Action flags | ||
### Command specific flags | ||
* `--dry-run` - the `socket report create` supports running the command without actually uploading anything. All CLI tools that perform an action should have a dry run flag | ||
* `--view` - when set on `socket report create` the command will immediately do a `socket report view` style view of the created report, waiting for the server to complete it | ||
@@ -41,2 +43,3 @@ ### Output flags | ||
* `--dry-run` - like all CLI tools that perform an action should have, we have a dry run flag. Eg. `socket report create` supports running the command without actually uploading anything | ||
* `--debug` - outputs additional debug output. Great for debugging, geeks and us who develop. Hopefully you will never _need_ it, but it can still be fun, right? | ||
@@ -51,2 +54,3 @@ * `--help` - prints the help for the current command. All CLI tools should have this flag | ||
## Contributing | ||
### Environment variables for development | ||
@@ -57,6 +61,10 @@ | ||
## Similar projects | ||
* [`@socketsecurity/sdk`](https://github.com/SocketDev/socket-sdk-js) - the SDK used in this CLI | ||
## See also | ||
* [`@socketsecurity/sdk`]('https://github.com/SocketDev/socket-sdk-js") - the SDK used in this CLI | ||
* [Announcement blog post](https://socket.dev/blog/announcing-socket-cli-preview) | ||
* [Socket API Reference](https://docs.socket.dev/reference) - the API used in this CLI | ||
* [Socket GitHub App](https://github.com/apps/socket-security) - the plug-and-play GitHub App |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
35648
18
911
66
+ Added@socketsecurity/sdk@0.4.0(transitive)
- Removed@socketsecurity/sdk@0.3.1(transitive)
Updated@socketsecurity/sdk@^0.4.0
Updatedpony-cause@^2.1.8