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
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1465,6 +1465,12 @@ jobs:
- name: Setup uv (Pipenv / pip legs)
if: matrix.pipenv != '' || matrix.pip != ''
uses: astral-sh/setup-uv@eb1897b8dc4b5d5bfe39a428a8f2304605e0983c # v7.0.0
with:
# Pin uv: it is the PEP 751 oracle the pylock tests install with,
# and uv 0.13.2 started refusing Pipenv's `environments = []`
# pylock ("target environment is not supported by the lockfile").
# Bump deliberately, after checking the Pipenv pylock legs pass.
version: "0.13.1"

- name: Install the Pythons the Pipenv / pip legs run on
if: matrix.pipenv != '' || matrix.pip != ''
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/pipenv-compatibility.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ jobs:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Install uv
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
with:
# Pin uv: it is the PEP 751 oracle the pylock tests install with,
# and uv 0.13.2 started refusing Pipenv's `environments = []`
# pylock ("target environment is not supported by the lockfile").
# Bump deliberately, after checking the Pipenv pylock legs pass.
version: "0.13.1"
- name: Install the Pythons the Pipenv releases run on
run: uv python install 3.8 3.12
- name: Build the CLI
Expand Down
2 changes: 1 addition & 1 deletion crates/socket-patch-cli/CLI_CONTRACT.md
Original file line number Diff line number Diff line change
Expand Up @@ -1117,7 +1117,7 @@ Honored global flags: `--json`, `--silent` (errors only), `--yes` (skip the conf

| Detected channel | Hint |
|---|---|
| npm family (`node_modules` path component) | The refusal names the owning manager (`managed by npm` / `pnpm` / `vlt`). **pnpm** (a `.pnpm` virtual-store component in the path, a `pnpm` home above a `global` store, or `pnpm-lock.yaml` beside the outermost `node_modules`): global store: `pnpm add -g @socketsecurity/socket-patch@latest`; project: `pnpm add @socketsecurity/socket-patch@latest`, or `pnpm update --recursive --latest @socketsecurity/socket-patch` when that directory holds `pnpm-workspace.yaml`. **npm / vlt** project-local (the directory holding the outermost `node_modules` has a `package.json`, and it is not directly under `lib`/`npm` or below a yarn/pnpm `global` store): `npm install @socketsecurity/socket-patch@latest`, or `vlt install @socketsecurity/socket-patch@latest` when that directory holds `vlt-lock.json`, or `vlx -y -- @socketsecurity/socket-patch@latest …` when its `package.json` is vlx's (`"name": "vlx"`, the vlx cache); otherwise global (including a yarn `global` store and version-manager prefixes such as nvm-windows and fnm): `npm update -g @socketsecurity/socket-patch` |
| npm family (`node_modules` path component) | The refusal names the owning manager (`managed by npm` / `pnpm` / `bun` / `vlt`). **pnpm** (a `.pnpm` virtual-store component in the path, a `pnpm` home above a `global` store, or `pnpm-lock.yaml` beside the outermost `node_modules`): global store: `pnpm add -g @socketsecurity/socket-patch@latest`; project: `pnpm add @socketsecurity/socket-patch@latest`, or `pnpm update --recursive --latest @socketsecurity/socket-patch` when that directory holds `pnpm-workspace.yaml`. **Bun** (Bun's `install/global` store, a `.bun` isolated-linker store component after the first `node_modules`, or `bun.lock` / `bun.lockb` beside the outermost `node_modules`; a store component in the path outranks a lockfile, and pnpm's lockfile outranks Bun's): bunx cache (that directory, or for a scoped spec its parent, is named `bunx-<numeric uid>-…`): `bunx @socketsecurity/socket-patch@latest …`; global store: `bun add -g @socketsecurity/socket-patch@latest` (`bun update -g` keeps the exact version the global manifest pins); project: `bun add @socketsecurity/socket-patch@latest`. **npm / vlt** project-local (the directory holding the outermost `node_modules` has a `package.json`, and it is not directly under `lib`/`npm` or below a yarn/pnpm `global` store): `npm install @socketsecurity/socket-patch@latest`, or `vlt install @socketsecurity/socket-patch@latest` when that directory holds `vlt-lock.json`, or `vlx -y -- @socketsecurity/socket-patch@latest …` when its `package.json` is vlx's (`"name": "vlx"`, the vlx cache); otherwise global (including a yarn `global` store and version-manager prefixes such as nvm-windows and fnm): `npm update -g @socketsecurity/socket-patch` |
| Legacy PyPI wheel (`site-packages`/`dist-packages`) | `pip uninstall socket-patch` followed by the standalone installer (macOS/Linux) or `npm install -g @socketsecurity/socket-patch` (Windows) |
| `cargo install` (`$CARGO_HOME/bin`, `~/.cargo/bin`) | `cargo install socket-patch-cli` |
| Legacy gem launcher cache (`<cache>/socket-patch/bin/…`) | `gem uninstall socket-patch` followed by the standalone installer (macOS/Linux) or `npm install -g @socketsecurity/socket-patch` (Windows) |
Expand Down
48 changes: 48 additions & 0 deletions crates/socket-patch-cli/tests/update/self_update_channels_e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,54 @@ async fn pnpm_global_refuses_with_pnpm_hint() {
install.assert_binary_intact();
}

/// #1621: a Bun project install refuses with Bun's own command. Running
/// `npm install …` there writes a stray package-lock.json and leaves
/// bun.lock stale, so the next `bun install --frozen-lockfile` fails.
#[tokio::test]
async fn bun_project_refuses_with_bun_hint() {
let install = staged_install_at("app/node_modules/@socketsecurity/socket-patch-x/bin");
std::fs::write(install.root.path().join("app/package.json"), "{}").unwrap();
std::fs::write(install.root.path().join("app/bun.lock"), "").unwrap();
let (code, _stdout, stderr) = run_installed(
&install,
&["--update", "--yes"],
&[("SOCKET_UPDATE_BASE_URL", DEAD_BASE_URL)],
);
assert_eq!(code, 1, "managed install must refuse.\nstderr:\n{stderr}");
assert!(stderr.contains("managed by bun"), "{stderr}");
assert!(
stderr.contains("`bun add @socketsecurity/socket-patch@latest`"),
"a Bun project must get Bun's upgrade command: {stderr}"
);
assert!(!stderr.contains("npm install"), "{stderr}");
install.assert_binary_intact();
}

/// #1621: Bun's global store (`$BUN_INSTALL/install/global`) is invisible
/// to `npm update -g`, so a Bun global install must be told to use
/// `bun add -g`.
#[tokio::test]
async fn bun_global_refuses_with_bun_hint() {
let install =
staged_install_at(".bun/install/global/node_modules/@socketsecurity/socket-patch-x/bin");
let (code, stdout, _stderr) = run_installed(
&install,
&["--update", "--yes", "--json"],
&[("SOCKET_UPDATE_BASE_URL", DEAD_BASE_URL)],
);
assert_eq!(code, 1, "managed install must refuse.\nstdout:\n{stdout}");
let env = common::parse_json_envelope(&stdout);
let message = env["error"]["message"].as_str().unwrap_or_default();
assert_eq!(env["error"]["code"], "managed_install", "{stdout}");
assert!(message.contains("managed by bun"), "{stdout}");
assert!(
message.contains("`bun add -g @socketsecurity/socket-patch@latest`"),
"{stdout}"
);
assert!(!message.contains("npm update -g"), "{stdout}");
install.assert_binary_intact();
}

/// An npm-bundled binary (any `node_modules` component) refuses with the
/// npm upgrade command — and the refusal happens before ANY release
/// traffic: a fully valid, newer release is mounted and its routes must
Expand Down
Loading
Loading