@@ -64,7 +64,9 @@ import {
6464 getNodeSqliteKysely ,
6565} from "./kysely-sync.js" ;
6666import { requireNodeSqlite } from "./node-sqlite.js" ;
67+ import { compareOpenClawReleaseVersions , parseRegistryNpmSpec } from "./npm-registry-spec.js" ;
6768import { isWithinDir } from "./path-safety.js" ;
69+ import { compareComparableSemver , parseComparableSemver } from "./semver-compare.js" ;
6870import {
6971 ensureDir ,
7072 existsDir ,
@@ -403,13 +405,95 @@ function legacyInstallRecordHasCurrentResolvedIdentity(params: {
403405 return Boolean ( legacyResolvedSpec && currentResolvedSpec === legacyResolvedSpec ) ;
404406}
405407
408+ function readNpmInstallRecordPackageName (
409+ record : InstalledPluginIndex [ "installRecords" ] [ string ] ,
410+ ) : string | undefined {
411+ const resolvedName = readInstallRecordStringField ( record , "resolvedName" ) ?. trim ( ) ;
412+ if ( resolvedName ) {
413+ return resolvedName ;
414+ }
415+ for ( const key of [ "resolvedSpec" , "spec" ] ) {
416+ const spec = readInstallRecordStringField ( record , key ) ;
417+ if ( ! spec ) {
418+ continue ;
419+ }
420+ const parsed = parseRegistryNpmSpec ( spec ) ;
421+ if ( parsed ?. name ) {
422+ return parsed . name ;
423+ }
424+ }
425+ return undefined ;
426+ }
427+
428+ function readNpmInstallRecordVersion (
429+ record : InstalledPluginIndex [ "installRecords" ] [ string ] ,
430+ ) : string | undefined {
431+ const resolvedVersion = readInstallRecordStringField ( record , "resolvedVersion" ) ?. trim ( ) ;
432+ if ( resolvedVersion ) {
433+ return resolvedVersion ;
434+ }
435+ const version = readInstallRecordStringField ( record , "version" ) ?. trim ( ) ;
436+ if ( version ) {
437+ return version ;
438+ }
439+ for ( const key of [ "resolvedSpec" , "spec" ] ) {
440+ const spec = readInstallRecordStringField ( record , key ) ;
441+ const parsed = spec ? parseRegistryNpmSpec ( spec ) : null ;
442+ if ( parsed ?. selectorKind === "exact-version" && parsed . selector ) {
443+ return parsed . selector ;
444+ }
445+ }
446+ return undefined ;
447+ }
448+
449+ function compareInstallRecordVersions (
450+ currentVersion : string ,
451+ legacyVersion : string ,
452+ ) : number | null {
453+ const openClawVersionComparison = compareOpenClawReleaseVersions ( currentVersion , legacyVersion ) ;
454+ if ( openClawVersionComparison !== null ) {
455+ return openClawVersionComparison ;
456+ }
457+ return compareComparableSemver (
458+ parseComparableSemver ( currentVersion , { normalizeLegacyDotBeta : true } ) ,
459+ parseComparableSemver ( legacyVersion , { normalizeLegacyDotBeta : true } ) ,
460+ ) ;
461+ }
462+
463+ function legacyNpmInstallRecordSupersededByCurrent ( params : {
464+ currentRecord : InstalledPluginIndex [ "installRecords" ] [ string ] ;
465+ legacyRecord : InstalledPluginIndex [ "installRecords" ] [ string ] ;
466+ } ) : boolean {
467+ const { currentRecord, legacyRecord } = params ;
468+ if ( currentRecord . source !== "npm" || legacyRecord . source !== "npm" ) {
469+ return false ;
470+ }
471+ const currentPackageName = readNpmInstallRecordPackageName ( currentRecord ) ;
472+ const legacyPackageName = readNpmInstallRecordPackageName ( legacyRecord ) ;
473+ if ( ! currentPackageName || currentPackageName !== legacyPackageName ) {
474+ return false ;
475+ }
476+ if ( ! readInstallRecordStringField ( currentRecord , "installPath" ) ) {
477+ return false ;
478+ }
479+ const currentVersion = readNpmInstallRecordVersion ( currentRecord ) ;
480+ const legacyVersion = readNpmInstallRecordVersion ( legacyRecord ) ;
481+ if ( ! currentVersion || ! legacyVersion ) {
482+ return false ;
483+ }
484+ return compareInstallRecordVersions ( currentVersion , legacyVersion ) === 1 ;
485+ }
486+
406487function legacyInstallRecordCoveredByCurrent (
407488 currentRecord : InstalledPluginIndex [ "installRecords" ] [ string ] ,
408489 legacyRecord : InstalledPluginIndex [ "installRecords" ] [ string ] ,
409490) : boolean {
410491 if ( currentRecord . source !== legacyRecord . source ) {
411492 return false ;
412493 }
494+ if ( legacyNpmInstallRecordSupersededByCurrent ( { currentRecord, legacyRecord } ) ) {
495+ return true ;
496+ }
413497 for ( const key of Object . keys ( legacyRecord ) . toSorted ( ) ) {
414498 const currentValue = readInstallRecordField ( currentRecord , key ) ;
415499 if ( currentValue === readInstallRecordField ( legacyRecord , key ) ) {
0 commit comments