diff --git a/tests/tools/test_osv_check.py b/tests/tools/test_osv_check.py index f99fd39ee19..177ff17102b 100644 --- a/tests/tools/test_osv_check.py +++ b/tests/tools/test_osv_check.py @@ -83,6 +83,37 @@ class TestParsePackageFromArgs: def test_only_flags(self): assert _parse_package_from_args(["-y", "--yes"], "npm") == (None, None) + def test_package_equals_form(self): + # `npx --package=@scope/pkg@1.0 some-bin` -> install target is the + # --package value, NOT the executed binary `some-bin`. + name, ver = _parse_package_from_args( + ["--package=@scope/pkg@1.0", "some-bin"], "npm" + ) + assert name == "@scope/pkg" + assert ver == "1.0" + + def test_package_space_form(self): + # `npx --package @scope/pkg some-bin` (value in the next token). + name, ver = _parse_package_from_args( + ["--package", "@scope/pkg@2.0", "some-bin"], "npm" + ) + assert name == "@scope/pkg" + assert ver == "2.0" + + def test_short_p_form(self): + # `npx -p left-pad@1.3.0 cli-cmd` -> package is left-pad, not cli-cmd. + name, ver = _parse_package_from_args( + ["-p", "left-pad@1.3.0", "cli-cmd"], "npm" + ) + assert name == "left-pad" + assert ver == "1.3.0" + + def test_plain_positional_still_works(self): + # Regression guard: bare positional with no --package flag is the pkg. + name, ver = _parse_package_from_args(["-y", "react@18.3.1"], "npm") + assert name == "react" + assert ver == "18.3.1" + class TestCheckPackageForMalware: def test_clean_package(self): diff --git a/tools/osv_check.py b/tools/osv_check.py index e094b272104..1f8a986e11b 100644 --- a/tools/osv_check.py +++ b/tools/osv_check.py @@ -82,11 +82,25 @@ def _parse_package_from_args( if not args: return None, None - # Skip flags to find the package token + # Skip flags to find the package token. + # Honor npx's explicit install target: --package=NAME / --package NAME and + # the -p NAME short form, which name a package distinct from the executed + # binary. Without this the first bare positional (often the command name) + # is mistaken for the package. package_token = None + take_next = False for arg in args: if not isinstance(arg, str): continue + if take_next: + package_token = arg + break + if arg in ("--package", "-p"): + take_next = True + continue + if arg.startswith("--package="): + package_token = arg[len("--package="):] + break if arg.startswith("-"): continue package_token = arg