Skip to content

Commit 0f5488d

Browse files
committed
fix: update correct installation and use npm install for reliable upgrade
- Detect if running from local node_modules; if so, update the project instead of global to fix "version still old" after upgrade - Use npm install -g @latest instead of npm update -g to force latest version (npm update may report "up to date" without upgrading) - Add Windows cache path support (LOCALAPPDATA) - Add cwd when spawning local npm install for project updates Change-Id: I7d9b2b1a77f1ce9084dc7a6a92f2ec1725be55f3 Co-developed-by: Cursor <noreply@cursor.com>
1 parent d2c8b09 commit 0f5488d

1 file changed

Lines changed: 66 additions & 17 deletions

File tree

src/utils/version-checker.ts

Lines changed: 66 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,53 @@ import latestVersion from 'latest-version';
66
import semver from 'semver';
77
import { execSync, spawn } from 'child_process';
88
import { createRequire } from 'module';
9-
import { readFileSync, writeFileSync, mkdirSync } from 'fs';
9+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
1010
import { dirname, join } from 'path';
1111
import { homedir, platform } from 'os';
12+
import { fileURLToPath } from 'url';
1213

1314
const require = createRequire(import.meta.url);
1415
const pkg = require('../../package.json');
1516

17+
/**
18+
* Detect if the running package is from a local project's node_modules.
19+
* If so, we must update the local project, not the global installation.
20+
* Returns { local: true, projectRoot } when running from node_modules of another project.
21+
*/
22+
function isRunningFromLocalProject(): { local: boolean; projectRoot?: string } {
23+
const currentFile = fileURLToPath(import.meta.url);
24+
const distDir = dirname(currentFile);
25+
// dist/utils/version-checker.js -> package root is ../../ from dist/
26+
const pkgRoot = join(distDir, '..', '..');
27+
const pkgRootNormalized = join(pkgRoot, 'package.json');
28+
29+
if (!existsSync(pkgRootNormalized)) {
30+
return { local: false };
31+
}
32+
33+
// Check if we're inside node_modules of a parent project
34+
const parts = pkgRoot.split(/[/\\]/);
35+
const nodeModulesIdx = parts.findIndex((p) => p === 'node_modules');
36+
if (nodeModulesIdx < 0) {
37+
// Running from project's own dist/ or from global - not a local dependency
38+
return { local: false };
39+
}
40+
41+
// Project root is the directory containing node_modules
42+
const projectRoot = join(...parts.slice(0, nodeModulesIdx));
43+
const projectPackageJson = join(projectRoot, 'package.json');
44+
if (existsSync(projectPackageJson)) {
45+
return { local: true, projectRoot };
46+
}
47+
return { local: false };
48+
}
49+
1650
interface UpdateInfo {
1751
current: string;
1852
latest: string;
1953
updateCommand: string;
54+
/** When updating local project, run npm in this directory */
55+
cwd?: string;
2056
}
2157

2258
interface UpdateCache {
@@ -148,10 +184,12 @@ export async function checkAndUpgrade(
148184
const hasUpdate = semver.gt(remoteLatest, pkg.version);
149185

150186
if (hasUpdate) {
187+
const { command, cwd } = getUpdateCommand();
151188
const updateInfo: UpdateInfo = {
152189
current: pkg.version,
153190
latest: remoteLatest,
154-
updateCommand: getUpdateCommand(),
191+
updateCommand: command,
192+
cwd,
155193
};
156194

157195
if (verbose) {
@@ -199,23 +237,31 @@ export async function checkAndUpgrade(
199237
}
200238

201239
/**
202-
* Get the appropriate update command based on installation method
240+
* Get the appropriate update command based on where the package is running from.
241+
* Must update the installation that is actually executing, not a different one.
203242
*/
204-
function getUpdateCommand(): string {
205-
try {
206-
// Check if installed globally
207-
execSync('npm list -g ' + pkg.name, { stdio: 'ignore' });
208-
return `npm update -g ${pkg.name} --yes --force`;
209-
} catch {
210-
// Check if installed locally
243+
function getUpdateCommand(): { command: string; cwd?: string } {
244+
const { local, projectRoot } = isRunningFromLocalProject();
245+
246+
if (local && projectRoot) {
247+
// Running from project's node_modules: update the local project
211248
try {
212-
execSync('npm list ' + pkg.name, { stdio: 'ignore' });
213-
return `npm update ${pkg.name} --yes --force`;
249+
execSync('npm list ' + pkg.name, {
250+
stdio: 'ignore',
251+
cwd: projectRoot,
252+
});
214253
} catch {
215-
// Fallback to install command
216-
return `npm install -g ${pkg.name}@latest --yes --force`;
254+
// Package not in package.json, install will add it
217255
}
256+
return {
257+
command: `npm install ${pkg.name}@latest --save-dev --yes --force`,
258+
cwd: projectRoot,
259+
};
218260
}
261+
262+
// Running from global or standalone: update global installation
263+
// Use npm install -g @latest to force latest (npm update may not update to latest)
264+
return { command: `npm install -g ${pkg.name}@latest --yes --force` };
219265
}
220266

221267
/**
@@ -244,9 +290,10 @@ async function performUpgrade(
244290
console.log(`🔧 Executing: ${cmd} ${args.join(' ')}`);
245291
}
246292

247-
const child = spawn(cmd, args, {
293+
const spawnOptions: Parameters<typeof spawn>[2] = {
248294
stdio: silent ? 'ignore' : 'inherit',
249295
shell: true,
296+
cwd: updateInfo.cwd,
250297
// Add non-interactive flags to prevent npm from waiting for user input
251298
env: {
252299
...process.env,
@@ -255,7 +302,9 @@ async function performUpgrade(
255302
// Prevent npm from asking for confirmation
256303
CI: 'true',
257304
},
258-
});
305+
};
306+
307+
const child = spawn(cmd, args, spawnOptions);
259308

260309
let resolved = false;
261310

@@ -377,7 +426,7 @@ export async function checkForUpdatesOnly(
377426
console.log('\n🔄 New version available!');
378427
console.log(`Current version: ${pkg.version}`);
379428
console.log(`Latest version: ${remoteLatest}`);
380-
console.log(`Update command: ${getUpdateCommand()}`);
429+
console.log(`Update command: ${getUpdateCommand().command}`);
381430
return true;
382431
}
383432

0 commit comments

Comments
 (0)