Skip to content

Commit 01afb8b

Browse files
committed
Consolidate coana launcher env vars into SOCKET_CLI_COANA_LAUNCHER
Replace the SOCKET_CLI_COANA_FORCE_NPM_INSTALL and SOCKET_CLI_COANA_DISABLE_NPM_FALLBACK toggles with a single SOCKET_CLI_COANA_LAUNCHER variable (auto | npx | npm-install), mirroring the Socket Node CLI. The legacy variables remain supported when the new variable is unset, but are deprecated and no longer documented. Follow-up from the review thread on PR #230.
1 parent 1af7934 commit 01afb8b

7 files changed

Lines changed: 100 additions & 12 deletions

File tree

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
# Changelog
22

3+
## 2.4.9
4+
5+
### Changed: consolidated coana launcher env vars into `SOCKET_CLI_COANA_LAUNCHER`
6+
7+
- The reachability launcher is now tuned via a single `SOCKET_CLI_COANA_LAUNCHER`
8+
environment variable (mirroring the Socket Node CLI): `auto` (default when unset; try
9+
`npx` first, fall back to `npm install` + `node` on launcher-level failures),
10+
`npm-install` (skip `npx` entirely), or `npx` (never fall back). An unrecognized value
11+
logs a warning and behaves as `auto`.
12+
- The legacy `SOCKET_CLI_COANA_FORCE_NPM_INSTALL` and `SOCKET_CLI_COANA_DISABLE_NPM_FALLBACK`
13+
variables remain supported for back-compat when `SOCKET_CLI_COANA_LAUNCHER` is unset, but
14+
are deprecated and no longer documented.
15+
316
## 2.4.8
417

518
### Fixed: retry transient full-scan upload failures

docs/cli-reference.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -316,9 +316,10 @@ For CI-specific examples and guidance, see [`ci-cd.md`](ci-cd.md).
316316
317317
The CLI runs a pinned `@coana-tech/cli` version via `npx --yes --force` (the same flags the Socket Node CLI passes for coana); it does **not** auto-update the engine or install it globally. `--yes` skips npx's interactive install prompt so non-interactive/CI runs don't hang. If the `npx` launcher is unavailable or fails before the engine starts, the CLI falls back to `npm install`-ing the pinned version into a temp directory and running it via `node`. Pass `--reach-version latest` to opt into the newest published version. Use `--reach` to enable reachability analysis during a full scan, or add `--only-facts-file` (with `--reach`) to submit only the reachability facts file (`.socket.facts.json`) when creating the full scan.
318318
319-
The launcher fallback can be tuned via environment variables:
320-
- `SOCKET_CLI_COANA_FORCE_NPM_INSTALL` — skip `npx` entirely and always use the `npm install` + `node` path (useful where `npx` is known-broken).
321-
- `SOCKET_CLI_COANA_DISABLE_NPM_FALLBACK` — never fall back; surface the `npx` failure directly.
319+
The launcher can be tuned via the `SOCKET_CLI_COANA_LAUNCHER` environment variable:
320+
- `auto` (default when unset) — try `npx` first; fall back to `npm install` + `node` if the launcher fails before the engine starts.
321+
- `npm-install` — skip `npx` entirely and always use the `npm install` + `node` path (useful where `npx` is known-broken).
322+
- `npx` — never fall back; surface the `npx` failure directly.
322323
323324
#### Advanced Configuration
324325
| Parameter | Required | Default | Description |

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
66

77
[project]
88
name = "socketsecurity"
9-
version = "2.4.8"
9+
version = "2.4.9"
1010
requires-python = ">= 3.11"
1111
license = {"file" = "LICENSE"}
1212
dependencies = [

socketsecurity/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
__author__ = 'socket.dev'
2-
__version__ = '2.4.8'
2+
__version__ = '2.4.9'
33
USER_AGENT = f'SocketPythonCLI/{__version__}'

socketsecurity/core/tools/reachability.py

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,31 @@ def _npx_launcher_failed_before_coana(returncode: int) -> bool:
298298
"""
299299
return returncode < 0 or returncode >= 128
300300

301+
@staticmethod
302+
def _resolve_coana_launcher_mode() -> str:
303+
"""Resolve the coana launcher mode: ``auto``, ``npx``, or ``npm-install``.
304+
305+
``SOCKET_CLI_COANA_LAUNCHER`` wins when set to a recognized value; an unrecognized
306+
value warns and behaves as ``auto``. Only when it is unset/empty do the legacy vars
307+
apply: ``SOCKET_CLI_COANA_FORCE_NPM_INSTALL`` -> ``npm-install``, else
308+
``SOCKET_CLI_COANA_DISABLE_NPM_FALLBACK`` -> ``npx``. Mirrors the Node CLI.
309+
"""
310+
raw = os.environ.get("SOCKET_CLI_COANA_LAUNCHER", "")
311+
mode = raw.strip().lower()
312+
if mode in ("auto", "npx", "npm-install"):
313+
return mode
314+
if mode:
315+
log.warning(
316+
f'Ignoring unrecognized SOCKET_CLI_COANA_LAUNCHER value "{raw}"; '
317+
'expected "auto", "npm-install", or "npx".'
318+
)
319+
return "auto"
320+
if os.environ.get("SOCKET_CLI_COANA_FORCE_NPM_INSTALL"):
321+
return "npm-install"
322+
if os.environ.get("SOCKET_CLI_COANA_DISABLE_NPM_FALLBACK"):
323+
return "npx"
324+
return "auto"
325+
301326
def _spawn_coana(
302327
self,
303328
coana_args: List[str],
@@ -318,14 +343,15 @@ def _spawn_coana(
318343
319344
Fallback path: if npx is missing, or its launcher dies before coana starts, install
320345
@coana-tech/cli into a temp dir via ``npm install`` and run it directly via ``node``.
321-
Toggle with ``SOCKET_CLI_COANA_FORCE_NPM_INSTALL`` (use the fallback as the primary
322-
path) and ``SOCKET_CLI_COANA_DISABLE_NPM_FALLBACK`` (never fall back).
346+
Tune with ``SOCKET_CLI_COANA_LAUNCHER``: ``auto`` (default; npx with the npm-install
347+
fallback), ``npm-install`` (skip npx, always use the fallback path), or ``npx``
348+
(never fall back).
323349
"""
324350
effective_version = self._resolve_coana_version(version)
325351
coana_env = self._sanitize_coana_env(env)
326-
disable_fallback = bool(os.environ.get("SOCKET_CLI_COANA_DISABLE_NPM_FALLBACK"))
352+
launcher_mode = self._resolve_coana_launcher_mode()
327353

328-
if os.environ.get("SOCKET_CLI_COANA_FORCE_NPM_INSTALL"):
354+
if launcher_mode == "npm-install":
329355
return self._spawn_coana_via_npm_install(coana_args, effective_version, coana_env, cwd)
330356

331357
package_spec = f"@coana-tech/cli@{effective_version}"
@@ -341,15 +367,15 @@ def _spawn_coana(
341367
)
342368
except FileNotFoundError:
343369
# npx is not on PATH: the launcher provably never started coana.
344-
if disable_fallback:
370+
if launcher_mode == "npx":
345371
raise
346372
log.warning("npx not found on PATH; retrying reachability analysis via `npm install` + `node`.")
347373
return self._spawn_coana_via_npm_install(coana_args, effective_version, coana_env, cwd)
348374

349375
if result.returncode == 0:
350376
return 0
351377

352-
if not disable_fallback and self._npx_launcher_failed_before_coana(result.returncode):
378+
if launcher_mode != "npx" and self._npx_launcher_failed_before_coana(result.returncode):
353379
log.warning(
354380
f"npx launcher failed (exit {result.returncode}) before coana started; "
355381
"retrying reachability analysis via `npm install` + `node`."

tests/unit/test_reachability.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,54 @@ def fake_run(argv, **_kw):
302302
assert all(c[:2] != ["npm", "install"] for c in calls)
303303

304304

305+
def test_launcher_npm_install_skips_npx(analyzer, mocker, monkeypatch):
306+
"""SOCKET_CLI_COANA_LAUNCHER=npm-install routes straight to npm install + node."""
307+
monkeypatch.setenv("SOCKET_CLI_COANA_LAUNCHER", "npm-install")
308+
calls = _capture_spawns(analyzer, mocker, npx_behavior=0)
309+
assert all(c[0] != "npx" for c in calls)
310+
assert calls[0][:2] == ["npm", "install"]
311+
assert calls[1][0] == "node"
312+
313+
314+
def test_launcher_npx_propagates_npx_failure(analyzer, mocker, monkeypatch):
315+
"""SOCKET_CLI_COANA_LAUNCHER=npx: a launcher failure is NOT retried via npm."""
316+
monkeypatch.setenv("SOCKET_CLI_COANA_LAUNCHER", "npx")
317+
mocker.patch.object(analyzer, "_extract_scan_id", return_value=None)
318+
calls = []
319+
320+
def fake_run(argv, **_kw):
321+
calls.append(argv)
322+
m = MagicMock()
323+
m.returncode = 137
324+
return m
325+
326+
mocker.patch.object(reachability.subprocess, "run", side_effect=fake_run)
327+
with pytest.raises(Exception):
328+
analyzer.run_reachability_analysis(org_slug="my-org", target_directory=".")
329+
assert calls[0][0] == "npx"
330+
assert all(c[:2] != ["npm", "install"] for c in calls)
331+
332+
333+
def test_launcher_overrides_legacy_vars(analyzer, mocker, monkeypatch):
334+
"""A recognized SOCKET_CLI_COANA_LAUNCHER wins; legacy vars are ignored entirely."""
335+
monkeypatch.setenv("SOCKET_CLI_COANA_LAUNCHER", "auto")
336+
monkeypatch.setenv("SOCKET_CLI_COANA_FORCE_NPM_INSTALL", "1")
337+
monkeypatch.setenv("SOCKET_CLI_COANA_DISABLE_NPM_FALLBACK", "1")
338+
calls = _capture_spawns(analyzer, mocker, npx_behavior=137)
339+
assert calls[0][0] == "npx" # force-npm-install ignored: npx still attempted
340+
assert calls[1][:2] == ["npm", "install"] # disable-fallback ignored: fallback runs
341+
assert calls[2][0] == "node"
342+
343+
344+
def test_launcher_unrecognized_value_behaves_as_auto(analyzer, mocker, monkeypatch):
345+
"""An unrecognized SOCKET_CLI_COANA_LAUNCHER value warns and behaves as auto."""
346+
monkeypatch.setenv("SOCKET_CLI_COANA_LAUNCHER", "bogus")
347+
calls = _capture_spawns(analyzer, mocker, npx_behavior=137)
348+
assert calls[0][0] == "npx"
349+
assert calls[1][:2] == ["npm", "install"]
350+
assert calls[2][0] == "node"
351+
352+
305353
def test_fallback_installs_once_per_version(analyzer, mocker):
306354
"""A second in-process fallback for the same version reuses the install (no re-install)."""
307355
mocker.patch.object(analyzer, "_extract_scan_id", return_value="scan-123")

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)