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
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 @@ -1213,7 +1213,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` / `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` |
| npm family (`node_modules` path component) | The refusal names the owning manager (`managed by npm` / `pnpm` / `bun` / `yarn` / `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`. **yarn** (yarn's global folder — the `global` directory holding the first `node_modules`, below a `yarn` home or `Yarn\Data` as `yarn global dir` prints on Linux, macOS and Windows, or holding `yarn.lock` itself, or a prefix-style `lib/node_modules` such as Volta's package image, whose `lib/` holds yarn's files after a `yarn global add` — or `yarn.lock` beside the outermost `node_modules` with no `package-lock.json` / `npm-shrinkwrap.json` there; pnpm's and Bun's stores and lockfiles outrank it): global folder: `yarn global add @socketsecurity/socket-patch@latest` (`npm update -g` never looks there); yarn 2+ project (`.yarnrc.yml`, a lock that starts with `__metadata`, or a `packageManager` of `yarn@2` or later): `yarn up @socketsecurity/socket-patch@latest`, which updates every workspace that declares it; yarn 1 project: `yarn add @socketsecurity/socket-patch@latest`; yarn 1 workspace root (a non-empty `workspaces` in that directory's `package.json`; yarn 1 refuses a bare `yarn add` there): `yarn upgrade @socketsecurity/socket-patch --latest` when the root `package.json` declares it, else `yarn upgrade-interactive --latest`, the one yarn 1 command that reaches a member's declaration. A global folder that is not named `global` (a custom `--global-folder`) reads as a yarn project. **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 `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 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
53 changes: 53 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 @@ -158,6 +158,59 @@ async fn bun_global_refuses_with_bun_hint() {
install.assert_binary_intact();
}

/// #1629: a yarn project install refuses with yarn's own command. Running
/// `npm install …` there writes a stray package-lock.json beside yarn.lock,
/// and every later yarn run warns about mixing package managers.
#[tokio::test]
async fn yarn_project_refuses_with_yarn_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/yarn.lock"),
"# yarn lockfile v1\n",
)
.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 yarn"), "{stderr}");
assert!(
stderr.contains("`yarn add @socketsecurity/socket-patch@latest`"),
"a yarn project must get yarn's upgrade command: {stderr}"
);
assert!(!stderr.contains("npm install"), "{stderr}");
install.assert_binary_intact();
}

/// #1629: yarn's global folder (`yarn global dir`) is invisible to
/// `npm update -g`, which prints "up to date" and leaves the yarn copy on
/// the old version, so a `yarn global add` install must be told to use
/// `yarn global add`.
#[tokio::test]
async fn yarn_global_refuses_with_yarn_hint() {
let install =
staged_install_at(".config/yarn/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 yarn"), "{stdout}");
assert!(
message.contains("`yarn global add @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