@@ -21,6 +21,8 @@ const semver = require('semver');
2121const { execSync } = require ( 'child_process' ) ;
2222
2323const PRO_PACKAGES = [ '@aiox-fullstack/pro' , '@aios-fullstack/pro' ] ;
24+ const CORE_PACKAGES = [ '@synkra/aiox-core' , 'aiox-core' ] ;
25+ const DEPENDENCY_FIELDS = [ 'dependencies' , 'devDependencies' , 'optionalDependencies' , 'peerDependencies' ] ;
2426const CORE_PACKAGE_ROOT = path . resolve ( __dirname , '..' , '..' , '..' ) ;
2527const INSTALLER_PACKAGE_ROOT = path . join ( CORE_PACKAGE_ROOT , 'packages' , 'installer' ) ;
2628
@@ -95,6 +97,71 @@ function resolveInstalledPro(projectRoot) {
9597 return null ;
9698}
9799
100+ function readProjectPackageJson ( projectRoot ) {
101+ const packageJsonPath = path . join ( projectRoot , 'package.json' ) ;
102+ if ( ! fs . existsSync ( packageJsonPath ) ) {
103+ return null ;
104+ }
105+
106+ try {
107+ return JSON . parse ( fs . readFileSync ( packageJsonPath , 'utf8' ) ) ;
108+ } catch {
109+ return null ;
110+ }
111+ }
112+
113+ function buildNodeModulesPackageJsonPath ( projectRoot , packageName ) {
114+ if ( packageName . startsWith ( '@' ) ) {
115+ const [ scope , name ] = packageName . slice ( 1 ) . split ( '/' ) ;
116+ return path . join ( projectRoot , 'node_modules' , scope , name , 'package.json' ) ;
117+ }
118+
119+ return path . join ( projectRoot , 'node_modules' , packageName , 'package.json' ) ;
120+ }
121+
122+ function detectCorePackageName ( projectRoot ) {
123+ const packageJson = readProjectPackageJson ( projectRoot ) ;
124+ if ( ! packageJson ) {
125+ return null ;
126+ }
127+
128+ if ( CORE_PACKAGES . includes ( packageJson . name ) ) {
129+ return packageJson . name ;
130+ }
131+
132+ for ( const field of DEPENDENCY_FIELDS ) {
133+ const dependencies = packageJson [ field ] || { } ;
134+ for ( const packageName of CORE_PACKAGES ) {
135+ if ( typeof dependencies [ packageName ] === 'string' ) {
136+ return packageName ;
137+ }
138+ }
139+ }
140+
141+ return null ;
142+ }
143+
144+ function assertValidProjectRoot ( projectRoot ) {
145+ if ( ! projectRoot || typeof projectRoot !== 'string' ) {
146+ throw new TypeError ( 'updatePro(projectRoot): projectRoot must be a non-empty string.' ) ;
147+ }
148+
149+ const resolvedProjectRoot = path . resolve ( projectRoot ) ;
150+
151+ let stats ;
152+ try {
153+ stats = fs . statSync ( resolvedProjectRoot ) ;
154+ } catch {
155+ throw new Error ( `updatePro(projectRoot): projectRoot does not exist or is not a directory: ${ resolvedProjectRoot } ` ) ;
156+ }
157+
158+ if ( ! stats . isDirectory ( ) ) {
159+ throw new Error ( `updatePro(projectRoot): projectRoot does not exist or is not a directory: ${ resolvedProjectRoot } ` ) ;
160+ }
161+
162+ return resolvedProjectRoot ;
163+ }
164+
98165/**
99166 * Get the installed aiox-core version.
100167 * @param {string } projectRoot
@@ -111,32 +178,34 @@ function getCoreVersion(projectRoot) {
111178 } catch { /* skip */ }
112179 }
113180
114- const packageJsonPath = path . join ( projectRoot , 'node_modules' , 'aiox-core' , 'package.json' ) ;
115- if ( fs . existsSync ( packageJsonPath ) ) {
116- try {
117- const data = JSON . parse ( fs . readFileSync ( packageJsonPath , 'utf8' ) ) ;
118- return data . version || null ;
119- } catch { /* skip */ }
181+ for ( const packageName of CORE_PACKAGES ) {
182+ const packageJsonPath = buildNodeModulesPackageJsonPath ( projectRoot , packageName ) ;
183+ if ( fs . existsSync ( packageJsonPath ) ) {
184+ try {
185+ const data = JSON . parse ( fs . readFileSync ( packageJsonPath , 'utf8' ) ) ;
186+ return data . version || null ;
187+ } catch { /* skip */ }
188+ }
120189 }
121190
122- const localPackageJsonPath = path . join ( projectRoot , 'package.json' ) ;
123- if ( fs . existsSync ( localPackageJsonPath ) ) {
124- try {
125- const data = JSON . parse ( fs . readFileSync ( localPackageJsonPath , 'utf8' ) ) ;
126- if ( data . name === '@synkra/aiox-core' || data . name === 'aiox-core' ) {
127- return data . version || null ;
128- }
191+ const projectPackageJson = readProjectPackageJson ( projectRoot ) ;
192+ if ( projectPackageJson ) {
193+ if ( CORE_PACKAGES . includes ( projectPackageJson . name ) ) {
194+ return projectPackageJson . version || null ;
195+ }
129196
130- for ( const field of [ 'dependencies' , 'devDependencies' , 'optionalDependencies' , 'peerDependencies' ] ) {
131- const declaredVersion = data [ field ] ?. [ 'aiox-core' ] ;
197+ const declaredCorePackage = detectCorePackageName ( projectRoot ) ;
198+ if ( declaredCorePackage ) {
199+ for ( const field of DEPENDENCY_FIELDS ) {
200+ const declaredVersion = projectPackageJson [ field ] ?. [ declaredCorePackage ] ;
132201 if ( typeof declaredVersion === 'string' ) {
133202 const parsed = semver . coerce ( declaredVersion ) ;
134203 if ( parsed ) {
135204 return parsed . version ;
136205 }
137206 }
138207 }
139- } catch { /* skip */ }
208+ }
140209 }
141210
142211 return null ;
@@ -231,6 +300,7 @@ function buildInstallCmd(pm, packageName) {
231300 * @returns {Promise<Object> } Update result
232301 */
233302async function updatePro ( projectRoot , options = { } ) {
303+ const resolvedProjectRoot = assertValidProjectRoot ( projectRoot ) ;
234304 const {
235305 check = false ,
236306 dryRun = false ,
@@ -254,7 +324,7 @@ async function updatePro(projectRoot, options = {}) {
254324
255325 // 1. Detect installed Pro
256326 onProgress ( 'detect' , 'Detecting installed Pro...' ) ;
257- const installed = resolveInstalledPro ( projectRoot ) ;
327+ const installed = resolveInstalledPro ( resolvedProjectRoot ) ;
258328
259329 if ( ! installed ) {
260330 result . error = 'AIOX Pro is not installed. Run: aiox pro setup' ;
@@ -266,7 +336,7 @@ async function updatePro(projectRoot, options = {}) {
266336 result . packageName = installed . packageName ;
267337
268338 // 2. Detect package manager
269- const pm = detectPackageManager ( projectRoot ) ;
339+ const pm = detectPackageManager ( resolvedProjectRoot ) ;
270340 result . packageManager = pm ;
271341
272342 // 3. Query npm for latest version
@@ -317,7 +387,7 @@ async function updatePro(projectRoot, options = {}) {
317387 } ) ;
318388
319389 // 5. Check compatibility with aiox-core
320- const coreVersion = getCoreVersion ( projectRoot ) ;
390+ const coreVersion = getCoreVersion ( resolvedProjectRoot ) ;
321391 const requiredCore = latest . peerDependencies ?. [ 'aiox-core' ] ;
322392
323393 if ( requiredCore && coreVersion && ! satisfiesPeer ( coreVersion , requiredCore ) ) {
@@ -337,7 +407,8 @@ async function updatePro(projectRoot, options = {}) {
337407 result . success = true ;
338408 result . actions . push ( { action : 'update' , status : 'dry_run' , command : buildInstallCmd ( pm , installed . packageName ) } ) ;
339409 if ( includeCoreUpdate ) {
340- result . actions . push ( { action : 'core_update' , status : 'dry_run' , command : buildInstallCmd ( pm , 'aiox-core' ) } ) ;
410+ const corePackageName = detectCorePackageName ( resolvedProjectRoot ) || 'aiox-core' ;
411+ result . actions . push ( { action : 'core_update' , status : 'dry_run' , command : buildInstallCmd ( pm , corePackageName ) } ) ;
341412 }
342413 if ( ! skipScaffold ) {
343414 result . actions . push ( { action : 'scaffold' , status : 'dry_run' } ) ;
@@ -349,8 +420,9 @@ async function updatePro(projectRoot, options = {}) {
349420 if ( includeCoreUpdate ) {
350421 onProgress ( 'core' , 'Updating aiox-core...' ) ;
351422 try {
352- const coreCmd = buildInstallCmd ( pm , 'aiox-core' ) ;
353- execSync ( coreCmd , { cwd : projectRoot , stdio : 'pipe' , timeout : 120000 } ) ;
423+ const corePackageName = detectCorePackageName ( resolvedProjectRoot ) || 'aiox-core' ;
424+ const coreCmd = buildInstallCmd ( pm , corePackageName ) ;
425+ execSync ( coreCmd , { cwd : resolvedProjectRoot , stdio : 'pipe' , timeout : 120000 } ) ;
354426 result . coreUpdated = true ;
355427 result . actions . push ( { action : 'core_update' , status : 'done' } ) ;
356428 } catch ( err ) {
@@ -364,7 +436,7 @@ async function updatePro(projectRoot, options = {}) {
364436 onProgress ( 'update' , `Updating ${ installed . packageName } to ${ latest . version } ...` ) ;
365437 try {
366438 const cmd = buildInstallCmd ( pm , installed . packageName ) ;
367- execSync ( cmd , { cwd : projectRoot , stdio : 'pipe' , timeout : 120000 } ) ;
439+ execSync ( cmd , { cwd : resolvedProjectRoot , stdio : 'pipe' , timeout : 120000 } ) ;
368440 result . actions . push ( { action : 'update' , status : 'done' , from : installed . version , to : latest . version } ) ;
369441 } catch ( err ) {
370442 result . error = `Failed to update ${ installed . packageName } : ${ err . message } ` ;
@@ -373,7 +445,7 @@ async function updatePro(projectRoot, options = {}) {
373445 }
374446
375447 // Re-read version after update
376- const updatedPro = resolveInstalledPro ( projectRoot ) ;
448+ const updatedPro = resolveInstalledPro ( resolvedProjectRoot ) ;
377449 if ( updatedPro ) {
378450 result . newVersion = updatedPro . version ;
379451 }
@@ -382,7 +454,7 @@ async function updatePro(projectRoot, options = {}) {
382454 if ( ! skipScaffold ) {
383455 const proPath = updatedPro ? updatedPro . packagePath : installed . packagePath ;
384456 const scaffolded = await applyScaffoldStep (
385- projectRoot ,
457+ resolvedProjectRoot ,
386458 proPath ,
387459 result ,
388460 onProgress ,
@@ -502,6 +574,7 @@ module.exports = {
502574 detectPackageManager,
503575 fetchLatestFromNpm,
504576 getCoreVersion,
577+ detectCorePackageName,
505578 satisfiesPeer,
506579 PRO_PACKAGES ,
507580} ;
0 commit comments