Skip to content
2 changes: 1 addition & 1 deletion packages/cli/src/commands/ask/cmd-ask.mts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ async function run(

if (!query) {
throw new InputError(
'Please provide a question.\n\nExample: socket ask "scan for vulnerabilities"',
'socket ask requires a QUERY positional argument; pass a question like `socket ask "scan for vulnerabilities"`',
)
}

Expand Down
9 changes: 7 additions & 2 deletions packages/cli/src/commands/audit-log/cmd-audit-log.mts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { handleAuditLog } from './handle-audit-log.mts'
import { FLAG_JSON, FLAG_MARKDOWN } from '../../constants/cli.mts'
import { outputDryRunFetch } from '../../utils/dry-run/output.mts'
import { InputError } from '../../utils/error/errors.mts'
import { V1_MIGRATION_GUIDE_URL } from '../../constants/socket.mjs'
import { commonFlags, outputFlags } from '../../flags.mts'
import { meowOrExit } from '../../utils/cli/with-subcommands.mjs'
Expand Down Expand Up @@ -191,10 +192,14 @@ async function run(
}

if (Number.isNaN(validatedPage) || validatedPage < 0) {
throw new Error(`Invalid value for --page: ${page}`)
throw new InputError(
`--page must be a non-negative integer (saw: "${page}"); pass a number like --page=1`,
)
}
if (Number.isNaN(validatedPerPage) || validatedPerPage < 0) {
throw new Error(`Invalid value for --per-page: ${perPage}`)
throw new InputError(
`--per-page must be a non-negative integer (saw: "${perPage}"); pass a number like --per-page=30`,
)
}

await handleAuditLog({
Expand Down
5 changes: 4 additions & 1 deletion packages/cli/src/commands/config/handle-config-set.mts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { debug, debugDir } from '@socketsecurity/lib/debug'

import { outputConfigSet } from './output-config-set.mts'
import { updateConfigValue } from '../../utils/config.mts'
import { InputError } from '../../utils/error/errors.mts'

import type { OutputKind } from '../../types.mts'
import type { LocalConfig } from '../../utils/config.mts'
Expand All @@ -16,7 +17,9 @@ export async function handleConfigSet({
outputKind: OutputKind
}) {
if (value === undefined) {
throw new Error('Value is required for config set')
throw new InputError(
`socket config set ${key} requires a VALUE argument; pass the value as the second positional (e.g. \`socket config set ${key} my-value\`)`,
)
}

debug(`Setting config ${key} = ${value}`)
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/commands/fix/cmd-fix.mts
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ async function run(
for (const ecosystem of ecosystemsRaw) {
if (!validEcosystemChoices.includes(ecosystem)) {
logger.fail(
`Invalid ecosystem: "${ecosystem}". Valid values are: ${joinAnd(validEcosystemChoices)}`,
`--ecosystems must be one of: ${joinAnd(validEcosystemChoices)} (saw: "${ecosystem}"); pass a supported ecosystem like --ecosystems=${validEcosystemChoices[0]}`,
)
process.exitCode = 1
return
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/src/commands/fix/coana-fix.mts
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,9 @@ export async function coanaFix(
if (ghsaIdsRaw && ghsaIdsRaw.trim()) {
const parsed = JSON.parse(ghsaIdsRaw)
if (!Array.isArray(parsed)) {
throw new Error('Expected array of GHSA IDs from coana output')
throw new Error(
`coana find-vulnerabilities returned non-array JSON on last line (got: ${typeof parsed}); expected an array of GHSA ID strings`,
)
}
discoveredIds.push(...parsed)
}
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/commands/login/cmd-login.mts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ async function run(

if (!isInteractive()) {
throw new InputError(
'Cannot prompt for credentials in a non-interactive shell. Use SOCKET_CLI_API_TOKEN environment variable instead',
'socket login needs an interactive TTY to prompt for credentials (stdin/stdout is not a TTY); set SOCKET_CLI_API_TOKEN in the environment instead',
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,9 @@ async function execGradleWithSpinner(
})

if (!output) {
throw new Error(`Failed to execute gradle: ${bin}`)
throw new Error(
`spawn returned no output for gradle (bin: ${bin}); check that the gradlew wrapper is executable and re-run with --verbose`,
)
}

pass = true
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { handleDependencies } from './handle-dependencies.mts'
import { FLAG_JSON, FLAG_MARKDOWN } from '../../constants/cli.mts'
import { outputDryRunFetch } from '../../utils/dry-run/output.mts'
import { InputError } from '../../utils/error/errors.mts'
import { commonFlags, outputFlags } from '../../flags.mts'
import { meowOrExit } from '../../utils/cli/with-subcommands.mjs'
import {
Expand Down Expand Up @@ -115,10 +116,14 @@ async function run(
}

if (Number.isNaN(validatedLimit) || validatedLimit < 0) {
throw new Error(`Invalid value for --limit: ${limit}`)
throw new InputError(
`--limit must be a non-negative integer (saw: "${limit}"); pass a number like --limit=50`,
)
}
if (Number.isNaN(validatedOffset) || validatedOffset < 0) {
throw new Error(`Invalid value for --offset: ${offset}`)
throw new InputError(
`--offset must be a non-negative integer (saw: "${offset}"); pass a number like --offset=0`,
)
}

await handleDependencies({
Expand Down
32 changes: 21 additions & 11 deletions packages/cli/src/commands/scan/cmd-scan-create.mts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { suggestTarget } from './suggest_target.mts'
import { validateReachabilityTarget } from './validate-reachability-target.mts'
import constants, { REQUIREMENTS_TXT, SOCKET_JSON } from '../../constants.mts'
import { outputDryRunUpload } from '../../utils/dry-run/output.mts'
import { InputError } from '../../utils/error/errors.mts'
import { commonFlags, outputFlags } from '../../flags.mts'
import { meowOrExit } from '../../utils/cli/with-subcommands.mts'
import { getEcosystemChoicesForMeow } from '../../utils/ecosystem/types.mts'
Expand Down Expand Up @@ -441,8 +442,8 @@ async function run(
const validEcosystems = getEcosystemChoicesForMeow()
for (const ecosystem of reachEcosystemsRaw) {
if (!validEcosystems.includes(ecosystem)) {
throw new Error(
`Invalid ecosystem: "${ecosystem}". Valid values are: ${joinAnd(validEcosystems)}`,
throw new InputError(
`--reach-ecosystems must be one of: ${joinAnd(validEcosystems)} (saw: "${ecosystem}"); pass a supported ecosystem like --reach-ecosystems=${validEcosystems[0]}`,
)
}
reachEcosystems.push(ecosystem as PURL_Type)
Expand Down Expand Up @@ -726,17 +727,24 @@ async function run(

// Validate numeric flag conversions.
const validatedPullRequest = Number(pullRequest)
if (pullRequest !== undefined && Number.isNaN(validatedPullRequest)) {
throw new Error(`Invalid number value for --pull-request: ${pullRequest}`)
if (
pullRequest !== undefined &&
(Number.isNaN(validatedPullRequest) ||
!Number.isInteger(validatedPullRequest) ||
validatedPullRequest < 0)
) {
throw new InputError(
`--pull-request must be a non-negative integer (saw: "${pullRequest}"); pass a number like --pull-request=42`,
)
Comment thread
cursor[bot] marked this conversation as resolved.
}

const validatedReachAnalysisMemoryLimit = Number(reachAnalysisMemoryLimit)
if (
reachAnalysisMemoryLimit !== undefined &&
Number.isNaN(validatedReachAnalysisMemoryLimit)
) {
throw new Error(
`Invalid number value for --reach-analysis-memory-limit: ${reachAnalysisMemoryLimit}`,
throw new InputError(
`--reach-analysis-memory-limit must be a number of megabytes (saw: "${reachAnalysisMemoryLimit}"); pass an integer like --reach-analysis-memory-limit=4096`,
)
}

Expand All @@ -745,18 +753,20 @@ async function run(
reachAnalysisTimeout !== undefined &&
Number.isNaN(validatedReachAnalysisTimeout)
) {
throw new Error(
`Invalid number value for --reach-analysis-timeout: ${reachAnalysisTimeout}`,
throw new InputError(
`--reach-analysis-timeout must be a number of seconds (saw: "${reachAnalysisTimeout}"); pass an integer like --reach-analysis-timeout=300`,
)
}

const validatedReachConcurrency = Number(reachConcurrency)
if (
reachConcurrency !== undefined &&
Number.isNaN(validatedReachConcurrency)
(Number.isNaN(validatedReachConcurrency) ||
!Number.isInteger(validatedReachConcurrency) ||
validatedReachConcurrency <= 0)
) {
throw new Error(
`Invalid number value for --reach-concurrency: ${reachConcurrency}`,
throw new InputError(
`--reach-concurrency must be a positive integer (saw: "${reachConcurrency}"); pass a number like --reach-concurrency=4`,
)
}

Expand Down
9 changes: 7 additions & 2 deletions packages/cli/src/commands/scan/cmd-scan-list.mts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { handleListScans } from './handle-list-scans.mts'
import { outputDryRunFetch } from '../../utils/dry-run/output.mts'
import { InputError } from '../../utils/error/errors.mts'
import { V1_MIGRATION_GUIDE_URL } from '../../constants/socket.mts'
import { commonFlags, outputFlags } from '../../flags.mts'
import { meowOrExit } from '../../utils/cli/with-subcommands.mjs'
Expand Down Expand Up @@ -201,10 +202,14 @@ async function run(
}

if (Number.isNaN(validatedPage) || validatedPage < 1) {
throw new Error(`Invalid value for --page: ${cli.flags['page']}`)
throw new InputError(
`--page must be a positive integer (saw: "${cli.flags['page']}"); pass a number like --page=1`,
)
}
if (Number.isNaN(validatedPerPage) || validatedPerPage < 1) {
throw new Error(`Invalid value for --per-page: ${cli.flags['perPage']}`)
throw new InputError(
`--per-page must be a positive integer (saw: "${cli.flags['perPage']}"); pass a number like --per-page=30`,
)
}

await handleListScans({
Expand Down
21 changes: 12 additions & 9 deletions packages/cli/src/commands/scan/cmd-scan-reach.mts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { reachabilityFlags } from './reachability-flags.mts'
import { suggestTarget } from './suggest_target.mts'
import { validateReachabilityTarget } from './validate-reachability-target.mts'
import { outputDryRunExecute } from '../../utils/dry-run/output.mts'
import { InputError } from '../../utils/error/errors.mts'
import { commonFlags, outputFlags } from '../../flags.mts'
import { meowOrExit } from '../../utils/cli/with-subcommands.mts'
import { getEcosystemChoicesForMeow } from '../../utils/ecosystem/types.mts'
Expand Down Expand Up @@ -171,8 +172,8 @@ async function run(
const validEcosystems = getEcosystemChoicesForMeow()
for (const ecosystem of reachEcosystemsRaw) {
if (!validEcosystems.includes(ecosystem)) {
throw new Error(
`Invalid ecosystem: "${ecosystem}". Valid values are: ${joinAnd(validEcosystems)}`,
throw new InputError(
`--reach-ecosystems must be one of: ${joinAnd(validEcosystems)} (saw: "${ecosystem}"); pass a supported ecosystem like --reach-ecosystems=${validEcosystems[0]}`,
)
}
reachEcosystems.push(ecosystem as PURL_Type)
Expand Down Expand Up @@ -277,8 +278,8 @@ async function run(
reachAnalysisMemoryLimit !== undefined &&
Number.isNaN(validatedReachAnalysisMemoryLimit)
) {
throw new Error(
`Invalid number value for --reach-analysis-memory-limit: ${reachAnalysisMemoryLimit}`,
throw new InputError(
`--reach-analysis-memory-limit must be a number of megabytes (saw: "${reachAnalysisMemoryLimit}"); pass an integer like --reach-analysis-memory-limit=4096`,
)
}

Expand All @@ -287,18 +288,20 @@ async function run(
reachAnalysisTimeout !== undefined &&
Number.isNaN(validatedReachAnalysisTimeout)
) {
throw new Error(
`Invalid number value for --reach-analysis-timeout: ${reachAnalysisTimeout}`,
throw new InputError(
`--reach-analysis-timeout must be a number of seconds (saw: "${reachAnalysisTimeout}"); pass an integer like --reach-analysis-timeout=300`,
)
}

const validatedReachConcurrency = Number(reachConcurrency)
if (
reachConcurrency !== undefined &&
Number.isNaN(validatedReachConcurrency)
(Number.isNaN(validatedReachConcurrency) ||
!Number.isInteger(validatedReachConcurrency) ||
validatedReachConcurrency <= 0)
) {
throw new Error(
`Invalid number value for --reach-concurrency: ${reachConcurrency}`,
throw new InputError(
`--reach-concurrency must be a positive integer (saw: "${reachConcurrency}"); pass a number like --reach-concurrency=4`,
)
}

Expand Down
5 changes: 4 additions & 1 deletion packages/cli/src/commands/threat-feed/cmd-threat-feed.mts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { getDefaultLogger } from '@socketsecurity/lib/logger'

import { handleThreatFeed } from './handle-threat-feed.mts'
import { outputDryRunFetch } from '../../utils/dry-run/output.mts'
import { InputError } from '../../utils/error/errors.mts'
import { commonFlags, outputFlags } from '../../flags.mts'
import { meowOrExit } from '../../utils/cli/with-subcommands.mjs'
import {
Expand Down Expand Up @@ -288,7 +289,9 @@ async function run(
return
}
if (Number.isNaN(validatedPerPage) || validatedPerPage < 1) {
throw new Error(`Invalid value for --per-page: ${cli.flags['perPage']}`)
throw new InputError(
`--per-page must be a positive integer (saw: "${cli.flags['perPage']}"); pass a number like --per-page=30`,
)
}

await handleThreatFeed({
Expand Down
12 changes: 11 additions & 1 deletion packages/cli/src/commands/wrapper/add-socket-wrapper.mts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { promises as fs } from 'node:fs'

import { getDefaultLogger } from '@socketsecurity/lib/logger'

import { FileSystemError, getErrorCause } from '../../utils/error/errors.mts'

const logger = getDefaultLogger()

export async function addSocketWrapper(file: string): Promise<void> {
Expand All @@ -10,7 +13,14 @@ export async function addSocketWrapper(file: string): Promise<void> {
'alias npm="socket npm"\nalias npx="socket npx"\n',
)
} catch (e) {
throw new Error(`There was an error setting up the alias: ${e}`)
// Don't include `file` in the message: display.formatErrorForDisplay
// appends `(${error.path})` automatically when FileSystemError carries
// a path, so embedding it here would show the filename twice.
throw new FileSystemError(
`failed to append socket aliases (${getErrorCause(e)}); check that the file exists and is writable`,
file,
(e as NodeJS.ErrnoException)?.code,
)
Comment thread
cursor[bot] marked this conversation as resolved.
Comment thread
cursor[bot] marked this conversation as resolved.
}
logger.success(
`The alias was added to ${file}. Running 'npm install' will now be wrapped in Socket's "safe npm" 🎉`,
Expand Down
8 changes: 5 additions & 3 deletions packages/cli/src/commands/wrapper/postinstall-wrapper.mts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { addSocketWrapper } from './add-socket-wrapper.mts'
import { checkSocketWrapperSetup } from './check-socket-wrapper-setup.mts'
import { getBashRcPath, getZshRcPath } from '../../constants/paths.mts'
import { getBashrcDetails } from '../../utils/cli/completion.mts'
import { getErrorCause } from '../../utils/error/errors.mjs'
import { FileSystemError, getErrorCause } from '../../utils/error/errors.mjs'
import { updateInstalledTabCompletionScript } from '../install/setup-tab-completion.mts'
const logger = getDefaultLogger()

Expand Down Expand Up @@ -85,8 +85,10 @@ async function setupSocketWrapper(query: string): Promise<void> {
await addSocketWrapper(zshRcPath)
}
} catch (e) {
throw new Error(
`There was an issue setting up the alias: ${getErrorCause(e)}`,
throw new FileSystemError(
`failed to add socket aliases to ${bashRcPath} / ${zshRcPath} (${getErrorCause(e)}); check that your shell rc files exist and are writable`,
undefined,
(e as NodeJS.ErrnoException)?.code,
)
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/test/integration/cli/cmd-ask.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ describe('socket ask', async () => {
'should error when no query provided',
async cmd => {
const { code, stdout } = await spawnSocketCli(binCliPath, cmd)
expect(stdout).toContain('Please provide a question')
expect(stdout).toContain('requires a QUERY positional argument')
expect(code, 'should exit with non-zero code').not.toBe(0)
},
)
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/test/integration/cli/cmd-scan-create.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -541,7 +541,7 @@ describe('socket scan create', async () => {
async cmd => {
const { code, stderr, stdout } = await spawnSocketCli(binCliPath, cmd)
const output = stdout + stderr
expect(output).toContain('Invalid ecosystem: "invalid-ecosystem"')
expect(output).toContain('(saw: "invalid-ecosystem")')
expect(
code,
'should exit with non-zero code when invalid ecosystem is provided',
Expand Down Expand Up @@ -661,7 +661,7 @@ describe('socket scan create', async () => {
async cmd => {
const { code, stderr, stdout } = await spawnSocketCli(binCliPath, cmd)
const output = stdout + stderr
expect(output).toContain('Invalid ecosystem: "invalid"')
expect(output).toContain('(saw: "invalid")')
expect(
code,
'should exit with non-zero code when invalid ecosystem provided',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ describe('socket scan reach - dry-run tests', async () => {
async cmd => {
const { code, stderr, stdout } = await spawnSocketCli(binCliPath, cmd)
const output = stdout + stderr
expect(output).toContain('Invalid ecosystem: "invalid-ecosystem"')
expect(output).toContain('(saw: "invalid-ecosystem")')
expect(code, 'should exit with non-zero code').not.toBe(0)
},
)
Expand Down Expand Up @@ -445,7 +445,7 @@ describe('socket scan reach - dry-run tests', async () => {
async cmd => {
const { code, stderr, stdout } = await spawnSocketCli(binCliPath, cmd)
const output = stdout + stderr
expect(output).toContain('Invalid ecosystem: "invalid1"')
expect(output).toContain('(saw: "invalid1")')
expect(code, 'should exit with non-zero code').not.toBe(0)
},
)
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/test/unit/commands/ask/cmd-ask.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ describe('cmd-ask', () => {

it('should throw InputError when no query provided', async () => {
await expect(cmdAsk.run([], importMeta, context)).rejects.toThrow(
'Please provide a question',
/socket ask requires a QUERY positional argument/,
)
})

Expand Down
Loading