@@ -6,17 +6,53 @@ import latestVersion from 'latest-version';
66import semver from 'semver' ;
77import { execSync , spawn } from 'child_process' ;
88import { createRequire } from 'module' ;
9- import { readFileSync , writeFileSync , mkdirSync } from 'fs' ;
9+ import { readFileSync , writeFileSync , mkdirSync , existsSync } from 'fs' ;
1010import { dirname , join } from 'path' ;
1111import { homedir , platform } from 'os' ;
12+ import { fileURLToPath } from 'url' ;
1213
1314const require = createRequire ( import . meta. url ) ;
1415const 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+
1650interface 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
2258interface 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