diff --git a/lib/commands/info/index.js b/lib/commands/info/index.js index 5ded7c7..7017979 100644 --- a/lib/commands/info/index.js +++ b/lib/commands/info/index.js @@ -20,10 +20,12 @@ export const info = { const name = parentName + ' info' const input = setupCommand(name, info.description, argv, importMeta) - const packageData = input && await fetchPackageData(input.pkgName, input.pkgVersion, input) - - if (packageData) { - formatPackageDataOutput(packageData, { name, ...input }) + if (input) { + const spinner = ora(`Looking up data for version ${input.pkgVersion} of ${input.pkgName}\n`).start() + const packageData = await fetchPackageData(input.pkgName, input.pkgVersion, input, spinner) + if (packageData) { + formatPackageDataOutput(packageData, { name, ...input }, spinner) + } } } } @@ -121,12 +123,12 @@ function setupCommand (name, description, argv, importMeta) { /** * @param {string} pkgName * @param {string} pkgVersion - * @param {Pick} context + * @param {Pick} context + * @param {import('ora').Ora} spinner * @returns {Promise} */ -async function fetchPackageData (pkgName, pkgVersion, { includeAllIssues, strict }) { +async function fetchPackageData (pkgName, pkgVersion, { includeAllIssues }, spinner) { const socketSdk = await setupSdk(getDefaultKey() || FREE_API_KEY) - const spinner = ora(`Looking up data for version ${pkgVersion} of ${pkgName}`).start() const result = await handleApiCall(socketSdk.getIssuesByNPMPackage(pkgName, pkgVersion), 'looking up package') const scoreResult = await handleApiCall(socketSdk.getScoreByNPMPackage(pkgName, pkgVersion), 'looking up package score') @@ -142,33 +144,8 @@ async function fetchPackageData (pkgName, pkgVersion, { includeAllIssues, strict const severityCount = getSeverityCount(result.data, includeAllIssues ? undefined : 'high') - if (objectSome(severityCount)) { - const issueSummary = formatSeverityCount(severityCount) - spinner[strict ? 'fail' : 'succeed'](`Package has these issues: ${issueSummary}`) - - // Return the alert types for critical and high alerts - const issueDetails = result.data.filter(d => d.value?.severity === 'high' || d.value?.severity === 'critical') - const uniqueIssues = issueDetails.reduce((/** @type {{ [key: string]: number }} */ acc, issue) => { - const { type } = issue - if (type) { - let count = 0 - if (!acc[type]) { - count += 1 - acc[type] = count - } else { - acc[type]++ - } - } - return acc - }, {}) - Object.keys(uniqueIssues).map(issue => { - if (uniqueIssues[issue] === 1) { - return console.log(`- ${issue}`) - } - return console.log(`- ${issue}: ${uniqueIssues[issue]}`) - }) - } else { - spinner.succeed('Package has no issues') + if (!objectSome(severityCount)) { + spinner.succeed('\nPackage has no issues') } return { @@ -181,14 +158,14 @@ async function fetchPackageData (pkgName, pkgVersion, { includeAllIssues, strict /** * @param {PackageData} packageData * @param {{ name: string } & CommandContext} context + * @param {import('ora').Ora} spinner * @returns {void} */ - function formatPackageDataOutput ({ data, severityCount, score }, { name, outputJson, outputMarkdown, pkgName, pkgVersion, strict }) { + function formatPackageDataOutput ({ data, severityCount, score }, { name, outputJson, outputMarkdown, pkgName, pkgVersion, strict }, spinner) { if (outputJson) { console.log(JSON.stringify(data, undefined, 2)) } else { console.log('\nPackage report card:') - const scoreResult = { 'Supply Chain Risk': Math.floor(score.supplyChainRisk.score * 100), 'Maintenance': Math.floor(score.maintenance.score * 100), @@ -198,9 +175,15 @@ async function fetchPackageData (pkgName, pkgVersion, { includeAllIssues, strict } Object.entries(scoreResult).map(score => console.log(`- ${score[0]}: ${formatScore(score[1])}`)) + // Package issues list + const issueSummary = formatSeverityCount(severityCount) + console.log('\n') + spinner[strict ? 'fail' : 'succeed'](`Package has these issues: ${issueSummary}`) + formatPackageIssuesDetails(data) + + // Link to issues list const format = new ChalkOrMarkdown(!!outputMarkdown) 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) { console.log(chalk.dim('\nOr rerun', chalk.italic(name), 'using the', chalk.italic('--json'), 'flag to get full JSON output')) @@ -212,6 +195,33 @@ async function fetchPackageData (pkgName, pkgVersion, { includeAllIssues, strict } } +/** + * @param {import('@socketsecurity/sdk').SocketSdkReturnType<'getIssuesByNPMPackage'>["data"]} packageData + * @returns {void[]} + */ +function formatPackageIssuesDetails (packageData) { + const issueDetails = packageData.filter(d => d.value?.severity === 'high' || d.value?.severity === 'critical') + const uniqueIssues = issueDetails.reduce((/** @type {{ [key: string]: number }} */ acc, issue) => { + const { type } = issue + if (type) { + let count = 0 + if (!acc[type]) { + count += 1 + acc[type] = count + } else { + acc[type]++ + } + } + return acc + }, {}) + return Object.keys(uniqueIssues).map(issue => { + if (uniqueIssues[issue] === 1) { + return console.log(`- ${issue}`) + } + return console.log(`- ${issue}: ${uniqueIssues[issue]}`) + }) +} + /** * @param {number} score * @returns {string}