Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
{
"root": true,
"plugins": ["jsdoc"],
"plugins": [
"jsdoc",
"unicorn"
],
"extends": [
"@socketsecurity",
"plugin:jsdoc/recommended"
Expand All @@ -24,6 +27,8 @@
"jsdoc/require-property-description": "off",
"jsdoc/require-returns-description": "off",
"jsdoc/require-yields": "off",
"jsdoc/valid-types": "off"
"jsdoc/valid-types": "off",

"unicorn/expiring-todo-comments": "warn"
}
}
4 changes: 3 additions & 1 deletion .github/workflows/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,6 @@ jobs:
no-lockfile: true
npm-test-script: 'test-ci'
node-versions: '14,16,18,19'
os: 'ubuntu-latest,windows-latest'
# We currently have some issues on Windows that will have to wait to be fixed
# os: 'ubuntu-latest,windows-latest'
os: 'ubuntu-latest'
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
/node_modules
/.env
/.nyc_output
/.vscode

# We're a library, so please, no lock files
/package-lock.json
Expand All @@ -15,3 +16,4 @@
!/lib/types/**/*.d.ts

# Library specific ones
!/.vscode/extensions.json
10 changes: 10 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"recommendations": [
"ryanluker.vscode-coverage-gutters",
"hbenl.vscode-test-explorer",
"hbenl.vscode-mocha-test-adapter",
"dbaeumer.vscode-eslint",
"gruntfuggly.todo-tree",
"editorconfig.editorconfig"
]
}
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,15 @@ socket report view QXU8PmK7LfH608RAwfIKdbcHgwEd_ZeWJ9QEGv05FJUQ
## Commands

* `socket info <package@version>` - looks up issues for a package
* `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 create <path(s)-to-folder-or-file>` - creates a report on [socket.dev](https://socket.dev/)

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.

Supports globbing such as `**/package.json`.

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)

* `socket report view <report-id>` - looks up issues and scores from a report

## Flags
Expand All @@ -48,6 +56,10 @@ socket report view QXU8PmK7LfH608RAwfIKdbcHgwEd_ZeWJ9QEGv05FJUQ
* `--help` - prints the help for the current command. All CLI tools should have this flag
* `--version` - prints the version of the tool. All CLI tools should have this flag

## Configuration files

The CLI reads and uses data from a [`socket.yml` file](https://docs.socket.dev/docs/socket-yml) in the folder you run it in. It supports the version 2 of the `socket.yml` file format and makes use of the `projectIgnorePaths` to excludes files when creating a report.

## Environment variables

* `SOCKET_SECURITY_API_KEY` - if set, this will be used as the API-key
Expand Down
137 changes: 20 additions & 117 deletions lib/commands/report/create.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
/* eslint-disable no-console */

import { stat } from 'node:fs/promises'
import path from 'node:path'

import meow from 'meow'
import ora from 'ora'
import { ErrorWithCause } from 'pony-cause'

import { handleApiCall, handleUnsuccessfulApiResponse } from '../../utils/api-helpers.js'
import { ChalkOrMarkdown, logSymbols } from '../../utils/chalk-markdown.js'
import { InputError } from '../../utils/errors.js'
import { printFlagList } from '../../utils/formatting.js'
import { createDebugLogger } from '../../utils/misc.js'
import { getPackageFiles } from '../../utils/path-resolve.js'
import { setupSdk } from '../../utils/sdk.js'
import { isErrnoException } from '../../utils/type-helpers.js'
import { readSocketConfig } from '../../utils/socket-config.js'
import { fetchReportData, formatReportDataOutput } from './view.js'

/** @type {import('../../utils/meow-with-subcommands').CliSubcommand} */
Expand Down Expand Up @@ -80,6 +78,17 @@ async function setupCommand (name, description, argv, importMeta) {
Usage
$ ${name} <paths-to-package-folders-and-files>

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.

Supports globbing such as "**/package.json".

Ignores any file specified in your project's ".gitignore", your project's
"socket.yml" file's "projectIgnorePaths" and also has a sensible set of
default ignores from the "ignore-by-default" module.

Options
${printFlagList({
'--all': 'Include all issues',
Expand All @@ -93,7 +102,7 @@ async function setupCommand (name, description, argv, importMeta) {

Examples
$ ${name} .
$ ${name} ../package-lock.json
$ ${name} '**/package.json'
$ ${name} /path/to/a/package.json /path/to/another/package.json
$ ${name} . --view --json
`, {
Expand Down Expand Up @@ -152,8 +161,12 @@ async function setupCommand (name, description, argv, importMeta) {

const debugLog = createDebugLogger(dryRun || cli.flags.debug)

// TODO: Allow setting a custom cwd and/or configFile path?
const cwd = process.cwd()
const packagePaths = await resolvePackagePaths(cwd, cli.input)
const absoluteConfigPath = path.join(cwd, 'socket.yml')

const config = await readSocketConfig(absoluteConfigPath)
const packagePaths = await getPackageFiles(cwd, cli.input, config, debugLog)

return {
cwd,
Expand All @@ -174,7 +187,7 @@ async function setupCommand (name, description, argv, importMeta) {
* @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:`))
debugLog('Uploading:', packagePaths.join(`\n${logSymbols.info} Uploading: `))

if (dryRun) {
return
Expand Down Expand Up @@ -210,113 +223,3 @@ function formatReportCreationOutput (data, { outputJson, outputMarkdown }) {

console.log('\nNew report: ' + format.hyperlink(data.id, data.url, { fallbackToUrl: true }))
}

// TODO: Add globbing support with support for ignoring, as a "./**/package.json" in a project also traverses eg. node_modules
/**
* Takes paths to folders and/or package.json / package-lock.json files and resolves to package.json + package-lock.json pairs (where feasible)
*
* @param {string} cwd
* @param {string[]} inputPaths
* @returns {Promise<string[]>}
* @throws {InputError}
*/
async function resolvePackagePaths (cwd, inputPaths) {
const packagePathLookups = inputPaths.map(async (filePath) => {
const packagePath = await resolvePackagePath(cwd, filePath)
return findComplementaryPackageFile(packagePath)
})

const packagePaths = await Promise.all(packagePathLookups)

const uniquePackagePaths = new Set(packagePaths.flat())

return [...uniquePackagePaths]
}

/**
* Resolves a package.json / package-lock.json path from a relative folder / file path
*
* @param {string} cwd
* @param {string} inputPath
* @returns {Promise<string>}
* @throws {InputError}
*/
async function resolvePackagePath (cwd, inputPath) {
const filePath = path.resolve(cwd, inputPath)
/** @type {string|undefined} */
let filePathAppended

try {
const fileStat = await stat(filePath)

if (fileStat.isDirectory()) {
filePathAppended = path.resolve(filePath, 'package.json')
}
} catch (err) {
if (isErrnoException(err) && err.code === 'ENOENT') {
throw new InputError(`Expected '${inputPath}' to point to an existing file or directory`)
}
throw new ErrorWithCause('Failed to resolve path to package.json', { cause: err })
}

if (filePathAppended) {
/** @type {import('node:fs').Stats} */
let filePathAppendedStat

try {
filePathAppendedStat = await stat(filePathAppended)
} catch (err) {
if (isErrnoException(err) && err.code === 'ENOENT') {
throw new InputError(`Expected directory '${inputPath}' to contain a package.json file`)
}
throw new ErrorWithCause('Failed to resolve package.json in directory', { cause: err })
}

if (!filePathAppendedStat.isFile()) {
throw new InputError(`Expected '${filePathAppended}' to be a file`)
}

return filePathAppended
}

return filePath
}

/**
* Finds any complementary file to a package.json or package-lock.json
*
* @param {string} packagePath
* @returns {Promise<string[]>}
* @throws {InputError}
*/
async function findComplementaryPackageFile (packagePath) {
const basename = path.basename(packagePath)
const dirname = path.dirname(packagePath)

if (basename === 'package-lock.json') {
// We need the package file as well
return [
packagePath,
path.resolve(dirname, 'package.json')
]
}

if (basename === 'package.json') {
const lockfilePath = path.resolve(dirname, 'package-lock.json')
try {
const lockfileStat = await stat(lockfilePath)
if (lockfileStat.isFile()) {
return [packagePath, lockfilePath]
}
} catch (err) {
if (isErrnoException(err) && err.code === 'ENOENT') {
return [packagePath]
}
throw new ErrorWithCause(`Unexpected error when finding a lockfile for '${packagePath}'`, { cause: err })
}

throw new InputError(`Encountered a non-file at lockfile path '${lockfilePath}'`)
}

throw new InputError(`Expected '${packagePath}' to point to a package.json or package-lock.json or to a folder containing a package.json`)
}
9 changes: 5 additions & 4 deletions lib/utils/misc.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { logSymbols } from './chalk-markdown.js'

/**
* @param {boolean|undefined} printDebugLogs
* @returns {typeof console.error}
*/
export function createDebugLogger (printDebugLogs) {
if (printDebugLogs) {
return printDebugLogs
// eslint-disable-next-line no-console
return console.error.bind(console)
}
return () => {}
? (...params) => console.error(logSymbols.info, ...params)
: () => {}
}

/**
Expand Down
Loading