fix(uninstall): remove the CLI binaries too, not just agent configs (#1254)
* fix(uninstall): remove the CLI binaries too, not just agent configs (#1071) `codegraph uninstall` swept agent configurations and stopped — every installed binary stayed behind, so `codegraph` still ran afterward. Three disconnected paths each removed a fraction of an installation (uninstall: configs; install.sh --uninstall: the bundle; npm preuninstall: configs + npm's own package), and none cleared a shadowed second install — the uninstall edition of the #1071 PATH shadow. The uninstall now PLANS every install present on the machine — the bundle layout(s) (running binary's own, the platform default, a custom CODEGRAPH_INSTALL_DIR), the npm global package (found by asking `npm root -g`, so nvm/fnm/volta prefixes resolve correctly), and the bin-dir launcher link (only when it verifiably points into a detected install) — confirms with the user, then removes them all. `--yes` skips the prompt; the new `--keep-cli` flag keeps the old configs-only behavior. Safety rules: a source checkout is reported, never deleted; a project-local npm install is left to the project; on unix the default install dir doubles as the machine state dir, so only the install artifacts (versions/, current) are removed there — telemetry choice and daemon records survive. Windows can't delete a running exe but can rename it (the in-place upgrade's trick): a locked node.exe is renamed aside and surfaced as a one-file leftover instead of failing the removal, and npm is routed through cmd.exe (a direct .cmd spawn EINVALs on modern Node). Planner/executor are split with injected side effects (the upgrade orchestrator's convention) and unit-tested across the shadow case, state-dir preservation, custom dirs, foreign-shim protection, and the locked-exe dance; validated end-to-end on macOS against a fake HOME. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(uninstall): key path math on the target platform, not the host Real-Windows validation caught it: the planner/executor used the host path module, so win32 fixtures were meaningless on a POSIX host and POSIX fixtures failed on the Windows VM. Same convention as detectInstallMethod now — path.win32/path.posix chosen by the injected platform. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(upgrade): route npm through cmd.exe on Windows — a direct npm.cmd spawn EINVALs on modern Node Found while validating the uninstall change on the Windows VM: upgradeNpm spawned npm.cmd without a shell, which every current Node rejects with EINVAL (the CVE-2024-27980 hardening) — so `codegraph upgrade` on a Windows npm install failed before doing anything. Verified live on the VM: spawnSync('npm.cmd') → EINVAL; cmd.exe /d /s /c npm → works. npmInvocation moves into the upgrade orchestrator (remove-binary imports it from there — same direction as its existing imports, no cycle), and the win32 test now pins the WORKING invocation instead of the broken one. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
47823944a3
commit
40aa092f5b
+18
-3
@@ -610,17 +610,32 @@ function upgradeWindowsBundle(
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* How to invoke npm. On Windows npm is a .cmd batch file, which Node refuses
|
||||
* to spawn without a shell (EINVAL since the CVE-2024-27980 hardening) — a
|
||||
* direct `npm.cmd` spawn fails on every current Node, so route it through
|
||||
* cmd.exe, the same way the surface-refresh step invokes the .cmd launcher.
|
||||
* (Verified live on the Windows VM: `spawnSync('npm.cmd')` → EINVAL;
|
||||
* `cmd.exe /d /s /c npm …` → works.)
|
||||
*/
|
||||
export function npmInvocation(platform: NodeJS.Platform, npmArgs: string[]): { cmd: string; args: string[] } {
|
||||
if (platform === 'win32') {
|
||||
return { cmd: 'cmd.exe', args: ['/d', '/s', '/c', ['npm', ...npmArgs].join(' ')] };
|
||||
}
|
||||
return { cmd: 'npm', args: npmArgs };
|
||||
}
|
||||
|
||||
function upgradeNpm(
|
||||
method: Extract<InstallMethod, { kind: 'npm' }>,
|
||||
versionSpec: string,
|
||||
deps: UpgradeDeps
|
||||
): number {
|
||||
const npm = deps.platform === 'win32' ? 'npm.cmd' : 'npm';
|
||||
const args = method.scope === 'global'
|
||||
? ['install', '-g', `${NPM_PACKAGE}@${versionSpec}`]
|
||||
: ['install', `${NPM_PACKAGE}@${versionSpec}`];
|
||||
deps.log(c.dim(`Running: ${npm} ${args.join(' ')}`));
|
||||
const code = deps.run(npm, args, process.env);
|
||||
deps.log(c.dim(`Running: npm ${args.join(' ')}`));
|
||||
const inv = npmInvocation(deps.platform, args);
|
||||
const code = deps.run(inv.cmd, inv.args, process.env);
|
||||
if (code !== 0) {
|
||||
deps.error(`npm exited with code ${code}.`);
|
||||
if (method.scope === 'global') {
|
||||
|
||||
Reference in New Issue
Block a user