Skip to content
Open
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
51 changes: 47 additions & 4 deletions packages/cli/src/commands/scan/cmd-scan-create.mts
Original file line number Diff line number Diff line change
Expand Up @@ -202,10 +202,28 @@ const generalFlags: MeowFlags = {
},
}

export const cmdScanCreate = {
description,
hidden,
run,
// Scan argv for `--default-branch=<non-bool-string>` (both kebab-case
// and yargs-parser's camelCase expansion). The flag is declared boolean,
// so meow coerces `--default-branch=main` to `true` and discards "main"
// — silently leaving the scan without a branch tag.
const DEFAULT_BRANCH_PREFIXES = ['--default-branch=', '--defaultBranch=']

function findDefaultBranchValueMisuse(
argv: readonly string[],
): { prefix: string; value: string } | undefined {
for (const arg of argv) {
const prefix = DEFAULT_BRANCH_PREFIXES.find(p => arg.startsWith(p))
if (!prefix) {
continue
}
const value = arg.slice(prefix.length)
const normalized = value.toLowerCase()
if (normalized === 'true' || normalized === 'false' || value === '') {
continue
}
return { prefix, value }
}
Comment thread
jdalton marked this conversation as resolved.
return undefined
}

async function run(
Expand Down Expand Up @@ -272,6 +290,25 @@ async function run(
`,
}

// Detect the common `--default-branch=main` misuse before meow parses.
// `--default-branch` is a boolean — meow/yargs-parser treats
// `--default-branch=main` as `defaultBranch=true` and silently drops
// the "main" portion, so the user's scan gets tagged without the
// intended branch name and doesn't appear in the Main/PR dashboard
// tabs. Fail fast with a suggestion toward the correct form.
const defaultBranchMisuse = findDefaultBranchValueMisuse(argv)
if (defaultBranchMisuse) {
const { prefix, value } = defaultBranchMisuse
// Strip the trailing `=` from the matched prefix when naming the
// canonical flag in the suggestion — users should always be told
// to use the kebab-case form.
logger.fail(
`"${prefix}${value}" looks like you meant the branch name "${value}".\n--default-branch is a boolean flag; pass the branch name with --branch instead:\n socket scan create --branch ${value} --default-branch`,
)
process.exitCode = 2
return
}

const cli = meowOrExit({
argv,
config,
Expand Down Expand Up @@ -680,3 +717,9 @@ async function run(
workspace: (workspace && String(workspace)) || '',
})
}

export const cmdScanCreate = {
description,
hidden,
run,
}
95 changes: 95 additions & 0 deletions packages/cli/test/unit/commands/scan/cmd-scan-create.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -1366,5 +1366,100 @@ describe('cmd-scan-create', () => {
expect(mockHandleCreateNewScan).not.toHaveBeenCalled()
})
})

describe('--default-branch misuse detection', () => {
// --default-branch is a boolean flag; meow silently discards the
// `=<value>` portion on `--default-branch=<name>`. Catch that
// pattern before meow parses so users get a clear error pointing
// at the right shape (`--branch <name> --default-branch`).
it('fails when --default-branch=<name> is passed with a branch name', async () => {
await cmdScanCreate.run(
['--org', 'test-org', '--default-branch=main', '.'],
importMeta,
context,
)

expect(process.exitCode).toBe(2)
expect(mockHandleCreateNewScan).not.toHaveBeenCalled()
expect(mockLogger.fail).toHaveBeenCalledWith(
expect.stringContaining(
'"--default-branch=main" looks like you meant the branch name "main"',
),
)
expect(mockLogger.fail).toHaveBeenCalledWith(
expect.stringContaining('--branch main --default-branch'),
)
})

it('also catches the camelCase --defaultBranch=<name> variant', async () => {
// yargs-parser expands camelCase, so users can type either
// form from the shell. See Cursor bugbot feedback on PR #1230.
await cmdScanCreate.run(
['--org', 'test-org', '--defaultBranch=main', '.'],
importMeta,
context,
)

expect(process.exitCode).toBe(2)
expect(mockHandleCreateNewScan).not.toHaveBeenCalled()
expect(mockLogger.fail).toHaveBeenCalledWith(
expect.stringContaining('looks like you meant the branch name "main"'),
)
// Error quotes the exact form the user typed so there's no
// confusion about whether the error applies to their input.
expect(mockLogger.fail).toHaveBeenCalledWith(
expect.stringContaining('"--defaultBranch=main"'),
)
})

it.each([
'--default-branch=true',
'--default-branch=false',
'--default-branch=TRUE',
])('allows %s (explicit boolean form)', async arg => {
mockHasDefaultApiToken.mockReturnValueOnce(true)

await cmdScanCreate.run(
[
'--org',
'test-org',
'--branch',
'main',
arg,
'.',
'--no-interactive',
],
importMeta,
context,
)

// meow parses the flag normally and flows through to handleCreateNewScan.
expect(mockLogger.fail).not.toHaveBeenCalledWith(
expect.stringContaining('looks like you meant the branch name'),
)
})

it('allows bare --default-branch (default truthy form)', async () => {
mockHasDefaultApiToken.mockReturnValueOnce(true)

await cmdScanCreate.run(
[
'--org',
'test-org',
'--branch',
'main',
'--default-branch',
'.',
'--no-interactive',
],
importMeta,
context,
)

expect(mockLogger.fail).not.toHaveBeenCalledWith(
expect.stringContaining('looks like you meant the branch name'),
)
})
})
})
})