1616
1717import * as path from 'path' ;
1818import { inject , injectable } from '@theia/core/shared/inversify' ;
19- import { PluginDeployerResolverContext } from '@theia/plugin-ext' ;
19+ import { PluginDeployerResolverContext , PluginDeployerHandler , PluginIdentifiers } from '@theia/plugin-ext' ;
2020import { LocalPluginDeployerResolver } from '@theia/plugin-ext/lib/main/node/resolvers/local-plugin-deployer-resolver' ;
2121import { PluginVSCodeEnvironment } from '../common/plugin-vscode-environment' ;
2222import { isVSCodePluginFile } from './plugin-vscode-file-handler' ;
23- import { existsInDeploymentDir , unpackToDeploymentDir } from './plugin-vscode-utils' ;
23+ import { existsInDeploymentDir , unpackToDeploymentDir , extractExtensionIdentityFromVsix } from './plugin-vscode-utils' ;
2424
2525@injectable ( )
2626export class LocalVSIXFilePluginDeployerResolver extends LocalPluginDeployerResolver {
2727 static LOCAL_FILE = 'local-file' ;
2828 static FILE_EXTENSION = '.vsix' ;
2929
3030 @inject ( PluginVSCodeEnvironment ) protected readonly environment : PluginVSCodeEnvironment ;
31+ @inject ( PluginDeployerHandler ) protected readonly pluginDeployerHandler : PluginDeployerHandler ;
3132
3233 protected get supportedScheme ( ) : string {
3334 return LocalVSIXFilePluginDeployerResolver . LOCAL_FILE ;
@@ -38,14 +39,48 @@ export class LocalVSIXFilePluginDeployerResolver extends LocalPluginDeployerReso
3839 }
3940
4041 async resolveFromLocalPath ( pluginResolverContext : PluginDeployerResolverContext , localPath : string ) : Promise < void > {
41- const extensionId = path . basename ( localPath , LocalVSIXFilePluginDeployerResolver . FILE_EXTENSION ) ;
42+ // Extract the true extension identity from the VSIX package.json
43+ // This prevents duplicate installations when the same extension is installed from VSIX files with different filenames
44+ // See: https://github.com/eclipse-theia/theia/issues/16845
45+ const components = await extractExtensionIdentityFromVsix ( localPath ) ;
4246
43- if ( await existsInDeploymentDir ( this . environment , extensionId ) ) {
44- console . log ( `[${ pluginResolverContext . getOriginId ( ) } ]: Target dir already exists in plugin deployment dir` ) ;
47+ if ( ! components ) {
48+ // Fallback to filename-based ID if package.json cannot be read
49+ // This maintains backward compatibility for edge cases
50+ const fallbackId = path . basename ( localPath , LocalVSIXFilePluginDeployerResolver . FILE_EXTENSION ) ;
51+ console . warn ( `[${ pluginResolverContext . getOriginId ( ) } ]: Could not read extension identity from VSIX, falling back to filename: ${ fallbackId } ` ) ;
52+
53+ if ( await existsInDeploymentDir ( this . environment , fallbackId ) ) {
54+ console . log ( `[${ pluginResolverContext . getOriginId ( ) } ]: Target dir already exists in plugin deployment dir` ) ;
55+ return ;
56+ }
57+
58+ const fallbackDeploymentDir = await unpackToDeploymentDir ( this . environment , localPath , fallbackId ) ;
59+ pluginResolverContext . addPlugin ( fallbackId , fallbackDeploymentDir ) ;
60+ return ;
61+ }
62+
63+ const unversionedId = PluginIdentifiers . componentsToUnversionedId ( components ) ;
64+ const versionedId = PluginIdentifiers . componentsToVersionedId ( components ) ;
65+
66+ // Check if an extension with this identity is already deployed in memory
67+ const existingPlugins = this . pluginDeployerHandler . getDeployedPluginsById ( unversionedId ) ;
68+ if ( existingPlugins . length > 0 ) {
69+ const existingVersions = existingPlugins . map ( p => p . metadata . model . version ) ;
70+ console . log (
71+ 'Extension ' + unversionedId + ' (version(s): ' + existingVersions . join ( ', ' ) + ') is already installed.\n' +
72+ 'Uninstall the existing extension before installing a new version from VSIX.'
73+ ) ;
74+ return ;
75+ }
76+
77+ // Check if the deployment directory already exists on disk
78+ if ( await existsInDeploymentDir ( this . environment , versionedId ) ) {
79+ console . log ( `[${ pluginResolverContext . getOriginId ( ) } ]: Extension "${ versionedId } " already exists in deployment dir` ) ;
4580 return ;
4681 }
4782
48- const extensionDeploymentDir = await unpackToDeploymentDir ( this . environment , localPath , extensionId ) ;
49- pluginResolverContext . addPlugin ( extensionId , extensionDeploymentDir ) ;
83+ const extensionDeploymentDir = await unpackToDeploymentDir ( this . environment , localPath , versionedId ) ;
84+ pluginResolverContext . addPlugin ( versionedId , extensionDeploymentDir ) ;
5085 }
5186}
0 commit comments