@theia/plugin-ext 1.27.0-next.39 → 1.27.0-next.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/lib/common/plugin-identifiers.d.ts +41 -0
  2. package/lib/common/plugin-identifiers.d.ts.map +1 -0
  3. package/lib/common/plugin-identifiers.js +81 -0
  4. package/lib/common/plugin-identifiers.js.map +1 -0
  5. package/lib/common/plugin-protocol.d.ts +28 -7
  6. package/lib/common/plugin-protocol.d.ts.map +1 -1
  7. package/lib/common/plugin-protocol.js +3 -1
  8. package/lib/common/plugin-protocol.js.map +1 -1
  9. package/lib/hosted/browser/hosted-plugin.d.ts +2 -2
  10. package/lib/hosted/browser/hosted-plugin.d.ts.map +1 -1
  11. package/lib/hosted/browser/hosted-plugin.js +20 -13
  12. package/lib/hosted/browser/hosted-plugin.js.map +1 -1
  13. package/lib/hosted/browser/worker/plugin-manifest-loader.d.ts.map +1 -1
  14. package/lib/hosted/browser/worker/plugin-manifest-loader.js +4 -1
  15. package/lib/hosted/browser/worker/plugin-manifest-loader.js.map +1 -1
  16. package/lib/hosted/node/hosted-plugin-deployer-handler.d.ts +10 -5
  17. package/lib/hosted/node/hosted-plugin-deployer-handler.d.ts.map +1 -1
  18. package/lib/hosted/node/hosted-plugin-deployer-handler.js +54 -12
  19. package/lib/hosted/node/hosted-plugin-deployer-handler.js.map +1 -1
  20. package/lib/hosted/node/hosted-plugin-process.d.ts +2 -2
  21. package/lib/hosted/node/hosted-plugin-process.d.ts.map +1 -1
  22. package/lib/hosted/node/hosted-plugin-process.js.map +1 -1
  23. package/lib/hosted/node/hosted-plugin.d.ts +2 -2
  24. package/lib/hosted/node/hosted-plugin.d.ts.map +1 -1
  25. package/lib/hosted/node/hosted-plugin.js.map +1 -1
  26. package/lib/hosted/node/metadata-scanner.d.ts +3 -2
  27. package/lib/hosted/node/metadata-scanner.d.ts.map +1 -1
  28. package/lib/hosted/node/metadata-scanner.js +8 -3
  29. package/lib/hosted/node/metadata-scanner.js.map +1 -1
  30. package/lib/hosted/node/plugin-manifest-loader.d.ts.map +1 -1
  31. package/lib/hosted/node/plugin-manifest-loader.js +3 -0
  32. package/lib/hosted/node/plugin-manifest-loader.js.map +1 -1
  33. package/lib/hosted/node/plugin-service.d.ts +18 -4
  34. package/lib/hosted/node/plugin-service.d.ts.map +1 -1
  35. package/lib/hosted/node/plugin-service.js +73 -18
  36. package/lib/hosted/node/plugin-service.js.map +1 -1
  37. package/lib/hosted/node/scanners/scanner-theia.d.ts.map +1 -1
  38. package/lib/hosted/node/scanners/scanner-theia.js +4 -2
  39. package/lib/hosted/node/scanners/scanner-theia.js.map +1 -1
  40. package/lib/main/node/handlers/plugin-theia-directory-handler.d.ts +6 -1
  41. package/lib/main/node/handlers/plugin-theia-directory-handler.d.ts.map +1 -1
  42. package/lib/main/node/handlers/plugin-theia-directory-handler.js +61 -20
  43. package/lib/main/node/handlers/plugin-theia-directory-handler.js.map +1 -1
  44. package/lib/main/node/handlers/plugin-theia-file-handler.d.ts +4 -0
  45. package/lib/main/node/handlers/plugin-theia-file-handler.d.ts.map +1 -1
  46. package/lib/main/node/handlers/plugin-theia-file-handler.js +25 -4
  47. package/lib/main/node/handlers/plugin-theia-file-handler.js.map +1 -1
  48. package/lib/main/node/plugin-cli-contribution.d.ts +3 -0
  49. package/lib/main/node/plugin-cli-contribution.d.ts.map +1 -1
  50. package/lib/main/node/plugin-cli-contribution.js +13 -0
  51. package/lib/main/node/plugin-cli-contribution.js.map +1 -1
  52. package/lib/main/node/plugin-deployer-directory-handler-context-impl.d.ts +1 -0
  53. package/lib/main/node/plugin-deployer-directory-handler-context-impl.d.ts.map +1 -1
  54. package/lib/main/node/plugin-deployer-directory-handler-context-impl.js +17 -0
  55. package/lib/main/node/plugin-deployer-directory-handler-context-impl.js.map +1 -1
  56. package/lib/main/node/plugin-deployer-file-handler-context-impl.d.ts.map +1 -1
  57. package/lib/main/node/plugin-deployer-file-handler-context-impl.js +0 -1
  58. package/lib/main/node/plugin-deployer-file-handler-context-impl.js.map +1 -1
  59. package/lib/main/node/plugin-deployer-impl.d.ts +5 -2
  60. package/lib/main/node/plugin-deployer-impl.d.ts.map +1 -1
  61. package/lib/main/node/plugin-deployer-impl.js +72 -28
  62. package/lib/main/node/plugin-deployer-impl.js.map +1 -1
  63. package/lib/main/node/plugin-ext-backend-module.d.ts.map +1 -1
  64. package/lib/main/node/plugin-ext-backend-module.js +2 -0
  65. package/lib/main/node/plugin-ext-backend-module.js.map +1 -1
  66. package/lib/main/node/plugin-server-handler.d.ts +3 -2
  67. package/lib/main/node/plugin-server-handler.d.ts.map +1 -1
  68. package/lib/main/node/plugin-server-handler.js +3 -0
  69. package/lib/main/node/plugin-server-handler.js.map +1 -1
  70. package/lib/main/node/plugin-uninstallation-manager.d.ts +12 -0
  71. package/lib/main/node/plugin-uninstallation-manager.d.ts.map +1 -0
  72. package/lib/main/node/plugin-uninstallation-manager.js +66 -0
  73. package/lib/main/node/plugin-uninstallation-manager.js.map +1 -0
  74. package/package.json +25 -24
  75. package/src/common/plugin-identifiers.ts +84 -0
  76. package/src/common/plugin-protocol.ts +32 -8
  77. package/src/hosted/browser/hosted-plugin.ts +22 -16
  78. package/src/hosted/browser/worker/plugin-manifest-loader.ts +4 -2
  79. package/src/hosted/node/hosted-plugin-deployer-handler.ts +64 -20
  80. package/src/hosted/node/hosted-plugin-process.ts +2 -2
  81. package/src/hosted/node/hosted-plugin.ts +2 -2
  82. package/src/hosted/node/metadata-scanner.ts +8 -6
  83. package/src/hosted/node/plugin-manifest-loader.ts +2 -0
  84. package/src/hosted/node/plugin-service.ts +79 -23
  85. package/src/hosted/node/scanners/scanner-theia.ts +5 -3
  86. package/src/main/node/handlers/plugin-theia-directory-handler.ts +56 -28
  87. package/src/main/node/handlers/plugin-theia-file-handler.ts +25 -4
  88. package/src/main/node/plugin-cli-contribution.ts +12 -0
  89. package/src/main/node/plugin-deployer-directory-handler-context-impl.ts +17 -1
  90. package/src/main/node/plugin-deployer-file-handler-context-impl.ts +0 -1
  91. package/src/main/node/plugin-deployer-impl.ts +75 -30
  92. package/src/main/node/plugin-ext-backend-module.ts +3 -0
  93. package/src/main/node/plugin-server-handler.ts +6 -2
  94. package/src/main/node/plugin-uninstallation-manager.ts +60 -0
@@ -25,7 +25,9 @@ import { PreferenceSchema, PreferenceSchemaProperties } from '@theia/core/lib/co
25
25
  import { ProblemMatcherContribution, ProblemPatternContribution, TaskDefinition } from '@theia/task/lib/common';
26
26
  import { ColorDefinition } from '@theia/core/lib/common/color';
27
27
  import { ResourceLabelFormatter } from '@theia/core/lib/common/label-protocol';
28
+ import { PluginIdentifiers } from './plugin-identifiers';
28
29
 
30
+ export { PluginIdentifiers };
29
31
  export const hostedServicePath = '/services/hostedPlugin';
30
32
 
31
33
  /**
@@ -38,7 +40,8 @@ export type PluginEngine = string;
38
40
  */
39
41
  export interface PluginPackage {
40
42
  name: string;
41
- publisher: string;
43
+ // The publisher is not guaranteed to be defined for unpublished plugins. https://github.com/microsoft/vscode-vsce/commit/a38657ece04c20e4fbde15d5ac1ed39ca51cb856
44
+ publisher: string | undefined;
42
45
  version: string;
43
46
  engines: {
44
47
  [type in PluginEngine]: string;
@@ -487,6 +490,8 @@ export interface PluginDeployerFileHandlerContext {
487
490
 
488
491
  export interface PluginDeployerDirectoryHandlerContext {
489
492
 
493
+ copy(origin: string, target: string): Promise<void>;
494
+
490
495
  pluginEntry(): PluginDeployerEntry;
491
496
 
492
497
  }
@@ -811,6 +816,7 @@ export interface PluginMetadata {
811
816
  model: PluginModel;
812
817
  lifecycle: PluginLifecycle;
813
818
  isUnderDevelopment?: boolean;
819
+ outOfSync: boolean;
814
820
  }
815
821
 
816
822
  export const MetadataProcessor = Symbol('MetadataProcessor');
@@ -837,6 +843,11 @@ export interface HostedPluginClient {
837
843
 
838
844
  export interface PluginDependencies {
839
845
  metadata: PluginMetadata
846
+ /**
847
+ * Actual listing of plugin dependencies.
848
+ * Mapping from {@link PluginIdentifiers.UnversionedId external representation} of plugin identity to a string
849
+ * that can be used to identify the resolver for the specific plugin case, e.g. with scheme `vscode://<id>`.
850
+ */
840
851
  mapping?: Map<string, string>
841
852
  }
842
853
 
@@ -845,14 +856,25 @@ export interface PluginDeployerHandler {
845
856
  deployFrontendPlugins(frontendPlugins: PluginDeployerEntry[]): Promise<void>;
846
857
  deployBackendPlugins(backendPlugins: PluginDeployerEntry[]): Promise<void>;
847
858
 
848
- getDeployedPlugin(pluginId: string): DeployedPlugin | undefined;
849
- undeployPlugin(pluginId: string): Promise<boolean>;
859
+ getDeployedPluginsById(pluginId: string): DeployedPlugin[];
860
+
861
+ getDeployedPlugin(pluginId: PluginIdentifiers.VersionedId): DeployedPlugin | undefined;
862
+ /**
863
+ * Removes the plugin from the location it originally resided on disk.
864
+ * Unless `--uncompressed-plugins-in-place` is passed to the CLI, this operation is safe.
865
+ */
866
+ uninstallPlugin(pluginId: PluginIdentifiers.VersionedId): Promise<boolean>;
867
+ /**
868
+ * Removes the plugin from the locations to which it had been deployed.
869
+ * This operation is not safe - references to deleted assets may remain.
870
+ */
871
+ undeployPlugin(pluginId: PluginIdentifiers.VersionedId): Promise<boolean>;
850
872
 
851
873
  getPluginDependencies(pluginToBeInstalled: PluginDeployerEntry): Promise<PluginDependencies | undefined>;
852
874
  }
853
875
 
854
876
  export interface GetDeployedPluginsParams {
855
- pluginIds: string[]
877
+ pluginIds: PluginIdentifiers.VersionedId[]
856
878
  }
857
879
 
858
880
  export interface DeployedPlugin {
@@ -867,7 +889,9 @@ export interface DeployedPlugin {
867
889
  export const HostedPluginServer = Symbol('HostedPluginServer');
868
890
  export interface HostedPluginServer extends JsonRpcServer<HostedPluginClient> {
869
891
 
870
- getDeployedPluginIds(): Promise<string[]>;
892
+ getDeployedPluginIds(): Promise<PluginIdentifiers.VersionedId[]>;
893
+
894
+ getUninstalledPluginIds(): Promise<readonly PluginIdentifiers.VersionedId[]>;
871
895
 
872
896
  getDeployedPlugins(params: GetDeployedPluginsParams): Promise<DeployedPlugin[]>;
873
897
 
@@ -899,8 +923,8 @@ export interface PluginServer {
899
923
  * @param type whether a plugin is installed by a system or a user, defaults to a user
900
924
  */
901
925
  deploy(pluginEntry: string, type?: PluginType): Promise<void>;
902
-
903
- undeploy(pluginId: string): Promise<void>;
926
+ uninstall(pluginId: PluginIdentifiers.VersionedId): Promise<void>;
927
+ undeploy(pluginId: PluginIdentifiers.VersionedId): Promise<void>;
904
928
 
905
929
  setStorageValue(key: string, value: KeysToAnyValues, kind: PluginStorageKind): Promise<boolean>;
906
930
  getStorageValue(key: string, kind: PluginStorageKind): Promise<KeysToAnyValues>;
@@ -925,7 +949,7 @@ export interface ServerPluginRunner {
925
949
  /**
926
950
  * Provides additional plugin ids.
927
951
  */
928
- getExtraDeployedPluginIds(): Promise<string[]>;
952
+ getExtraDeployedPluginIds(): Promise<PluginIdentifiers.VersionedId[]>;
929
953
 
930
954
  }
931
955
 
@@ -25,7 +25,7 @@ import debounce = require('@theia/core/shared/lodash.debounce');
25
25
  import { UUID } from '@theia/core/shared/@phosphor/coreutils';
26
26
  import { injectable, inject, interfaces, named, postConstruct } from '@theia/core/shared/inversify';
27
27
  import { PluginWorker } from './plugin-worker';
28
- import { PluginMetadata, getPluginId, HostedPluginServer, DeployedPlugin, PluginServer } from '../../common/plugin-protocol';
28
+ import { PluginMetadata, getPluginId, HostedPluginServer, DeployedPlugin, PluginServer, PluginIdentifiers } from '../../common/plugin-protocol';
29
29
  import { HostedPluginWatcher } from './hosted-plugin-watcher';
30
30
  import { MAIN_RPC_CONTEXT, PluginManagerExt, ConfigStorage, UIKind } from '../../common/plugin-api-rpc';
31
31
  import { setUpPluginApi } from '../../main/browser/main-context';
@@ -165,7 +165,7 @@ export class HostedPluginSupport {
165
165
 
166
166
  protected readonly managers = new Map<string, PluginManagerExt>();
167
167
 
168
- private readonly contributions = new Map<string, PluginContributions>();
168
+ private readonly contributions = new Map<PluginIdentifiers.UnversionedId, PluginContributions>();
169
169
 
170
170
  protected readonly activationEvents = new Set<string>();
171
171
 
@@ -240,7 +240,7 @@ export class HostedPluginSupport {
240
240
  return plugins;
241
241
  }
242
242
 
243
- getPlugin(id: string): DeployedPlugin | undefined {
243
+ getPlugin(id: PluginIdentifiers.UnversionedId): DeployedPlugin | undefined {
244
244
  const contributions = this.contributions.get(id);
245
245
  return contributions && contributions.plugin;
246
246
  }
@@ -312,27 +312,33 @@ export class HostedPluginSupport {
312
312
  let syncPluginsMeasurement: Measurement | undefined;
313
313
 
314
314
  const toUnload = new Set(this.contributions.keys());
315
+ let didChangeInstallationStatus = false;
315
316
  try {
316
- const pluginIds: string[] = [];
317
- const deployedPluginIds = await this.server.getDeployedPluginIds();
317
+ const newPluginIds: PluginIdentifiers.VersionedId[] = [];
318
+ const [deployedPluginIds, uninstalledPluginIds] = await Promise.all([this.server.getDeployedPluginIds(), this.server.getUninstalledPluginIds()]);
318
319
  waitPluginsMeasurement.log('Waiting for backend deployment');
319
320
  syncPluginsMeasurement = this.measure('syncPlugins');
320
- for (const pluginId of deployedPluginIds) {
321
- toUnload.delete(pluginId);
322
- if (!this.contributions.has(pluginId)) {
323
- pluginIds.push(pluginId);
321
+ for (const versionedId of deployedPluginIds) {
322
+ const unversionedId = PluginIdentifiers.unversionedFromVersioned(versionedId);
323
+ toUnload.delete(unversionedId);
324
+ if (!this.contributions.has(unversionedId)) {
325
+ newPluginIds.push(versionedId);
324
326
  }
325
327
  }
326
328
  for (const pluginId of toUnload) {
327
- const contribution = this.contributions.get(pluginId);
328
- if (contribution) {
329
- contribution.dispose();
329
+ this.contributions.get(pluginId)?.dispose();
330
+ }
331
+ for (const versionedId of uninstalledPluginIds) {
332
+ const plugin = this.getPlugin(PluginIdentifiers.unversionedFromVersioned(versionedId));
333
+ if (plugin && PluginIdentifiers.componentsToVersionedId(plugin.metadata.model) === versionedId && !plugin.metadata.outOfSync) {
334
+ didChangeInstallationStatus = true;
335
+ plugin.metadata.outOfSync = didChangeInstallationStatus = true;
330
336
  }
331
337
  }
332
- if (pluginIds.length) {
333
- const plugins = await this.server.getDeployedPlugins({ pluginIds });
338
+ if (newPluginIds.length) {
339
+ const plugins = await this.server.getDeployedPlugins({ pluginIds: newPluginIds });
334
340
  for (const plugin of plugins) {
335
- const pluginId = plugin.metadata.model.id;
341
+ const pluginId = PluginIdentifiers.componentsToUnversionedId(plugin.metadata.model);
336
342
  const contributions = new PluginContributions(plugin);
337
343
  this.contributions.set(pluginId, contributions);
338
344
  contributions.push(Disposable.create(() => this.contributions.delete(pluginId)));
@@ -340,7 +346,7 @@ export class HostedPluginSupport {
340
346
  }
341
347
  }
342
348
  } finally {
343
- if (initialized || toUnload.size) {
349
+ if (initialized || toUnload.size || didChangeInstallationStatus) {
344
350
  this.onDidChangePluginsEmitter.fire(undefined);
345
351
  }
346
352
 
@@ -16,7 +16,7 @@
16
16
 
17
17
  /* eslint-disable @typescript-eslint/no-explicit-any */
18
18
 
19
- import { PluginModel, PluginPackage } from '../../../common/plugin-protocol';
19
+ import { PluginIdentifiers, PluginModel, PluginPackage } from '../../../common/plugin-protocol';
20
20
  import { Endpoint } from '@theia/core/lib/browser/endpoint';
21
21
  import URI from '@theia/core/lib/common/uri';
22
22
 
@@ -54,7 +54,9 @@ function readContents(uri: string): Promise<string> {
54
54
 
55
55
  async function readPluginJson(pluginModel: PluginModel, relativePath: string): Promise<any> {
56
56
  const content = await readPluginFile(pluginModel, relativePath);
57
- return JSON.parse(content);
57
+ const json = JSON.parse(content) as PluginPackage;
58
+ json.publisher ??= PluginIdentifiers.UNPUBLISHED;
59
+ return json;
58
60
  }
59
61
 
60
62
  export async function loadManifest(pluginModel: PluginModel): Promise<any> {
@@ -17,11 +17,15 @@
17
17
  import * as fs from '@theia/core/shared/fs-extra';
18
18
  import { injectable, inject } from '@theia/core/shared/inversify';
19
19
  import { ILogger } from '@theia/core';
20
- import { PluginDeployerHandler, PluginDeployerEntry, PluginEntryPoint, DeployedPlugin, PluginDependencies, PluginType } from '../../common/plugin-protocol';
20
+ import {
21
+ PluginDeployerHandler, PluginDeployerEntry, PluginEntryPoint, DeployedPlugin,
22
+ PluginDependencies, PluginType, PluginIdentifiers
23
+ } from '../../common/plugin-protocol';
21
24
  import { HostedPluginReader } from './plugin-reader';
22
25
  import { Deferred } from '@theia/core/lib/common/promise-util';
23
26
  import { HostedPluginLocalizationService } from './hosted-plugin-localization-service';
24
27
  import { Stopwatch } from '@theia/core/lib/common';
28
+ import { PluginUninstallationManager } from '../../main/node/plugin-uninstallation-manager';
25
29
 
26
30
  @injectable()
27
31
  export class HostedPluginDeployerHandler implements PluginDeployerHandler {
@@ -38,42 +42,56 @@ export class HostedPluginDeployerHandler implements PluginDeployerHandler {
38
42
  @inject(Stopwatch)
39
43
  protected readonly stopwatch: Stopwatch;
40
44
 
41
- private readonly deployedLocations = new Map<string, Set<string>>();
45
+ @inject(PluginUninstallationManager)
46
+ protected readonly uninstallationManager: PluginUninstallationManager;
47
+
48
+ private readonly deployedLocations = new Map<PluginIdentifiers.VersionedId, Set<string>>();
49
+ protected readonly originalLocations = new Map<PluginIdentifiers.VersionedId, string>();
42
50
 
43
51
  /**
44
52
  * Managed plugin metadata backend entries.
45
53
  */
46
- private readonly deployedBackendPlugins = new Map<string, DeployedPlugin>();
54
+ private readonly deployedBackendPlugins = new Map<PluginIdentifiers.VersionedId, DeployedPlugin>();
47
55
 
48
56
  /**
49
57
  * Managed plugin metadata frontend entries.
50
58
  */
51
- private readonly deployedFrontendPlugins = new Map<string, DeployedPlugin>();
59
+ private readonly deployedFrontendPlugins = new Map<PluginIdentifiers.VersionedId, DeployedPlugin>();
52
60
 
53
61
  private backendPluginsMetadataDeferred = new Deferred<void>();
54
62
 
55
63
  private frontendPluginsMetadataDeferred = new Deferred<void>();
56
64
 
57
- async getDeployedFrontendPluginIds(): Promise<string[]> {
65
+ async getDeployedFrontendPluginIds(): Promise<PluginIdentifiers.VersionedId[]> {
58
66
  // await first deploy
59
67
  await this.frontendPluginsMetadataDeferred.promise;
60
68
  // fetch the last deployed state
61
- return [...this.deployedFrontendPlugins.keys()];
69
+ return Array.from(this.deployedFrontendPlugins.keys());
62
70
  }
63
71
 
64
- async getDeployedBackendPluginIds(): Promise<string[]> {
72
+ async getDeployedBackendPluginIds(): Promise<PluginIdentifiers.VersionedId[]> {
65
73
  // await first deploy
66
74
  await this.backendPluginsMetadataDeferred.promise;
67
75
  // fetch the last deployed state
68
- return [...this.deployedBackendPlugins.keys()];
76
+ return Array.from(this.deployedBackendPlugins.keys());
69
77
  }
70
78
 
71
- getDeployedPlugin(pluginId: string): DeployedPlugin | undefined {
72
- const metadata = this.deployedBackendPlugins.get(pluginId);
73
- if (metadata) {
74
- return metadata;
75
- }
76
- return this.deployedFrontendPlugins.get(pluginId);
79
+ getDeployedPluginsById(pluginId: string): DeployedPlugin[] {
80
+ const matches: DeployedPlugin[] = [];
81
+ const handle = (plugins: Iterable<DeployedPlugin>): void => {
82
+ for (const plugin of plugins) {
83
+ if (PluginIdentifiers.componentsToVersionWithId(plugin.metadata.model).version === pluginId) {
84
+ matches.push(plugin);
85
+ }
86
+ }
87
+ };
88
+ handle(this.deployedFrontendPlugins.values());
89
+ handle(this.deployedBackendPlugins.values());
90
+ return matches;
91
+ }
92
+
93
+ getDeployedPlugin(pluginId: PluginIdentifiers.VersionedId): DeployedPlugin | undefined {
94
+ return this.deployedBackendPlugins.get(pluginId) ?? this.deployedFrontendPlugins.get(pluginId);
77
95
  }
78
96
 
79
97
  /**
@@ -123,6 +141,8 @@ export class HostedPluginDeployerHandler implements PluginDeployerHandler {
123
141
  protected async deployPlugin(entry: PluginDeployerEntry, entryPoint: keyof PluginEntryPoint): Promise<void> {
124
142
  const pluginPath = entry.path();
125
143
  const deployPlugin = this.stopwatch.start('deployPlugin');
144
+ let id;
145
+ let success = true;
126
146
  try {
127
147
  const manifest = await this.reader.readPackage(pluginPath);
128
148
  if (!manifest) {
@@ -133,12 +153,15 @@ export class HostedPluginDeployerHandler implements PluginDeployerHandler {
133
153
  const metadata = this.reader.readMetadata(manifest);
134
154
  metadata.isUnderDevelopment = entry.getValue('isUnderDevelopment') ?? false;
135
155
 
136
- const deployedLocations = this.deployedLocations.get(metadata.model.id) || new Set<string>();
156
+ id = PluginIdentifiers.componentsToVersionedId(metadata.model);
157
+
158
+ const deployedLocations = this.deployedLocations.get(id) || new Set<string>();
137
159
  deployedLocations.add(entry.rootPath);
138
- this.deployedLocations.set(metadata.model.id, deployedLocations);
160
+ this.deployedLocations.set(id, deployedLocations);
161
+ this.originalLocations.set(id, entry.originalPath());
139
162
 
140
163
  const deployedPlugins = entryPoint === 'backend' ? this.deployedBackendPlugins : this.deployedFrontendPlugins;
141
- if (deployedPlugins.has(metadata.model.id)) {
164
+ if (deployedPlugins.has(id)) {
142
165
  deployPlugin.debug(`Skipped ${entryPoint} plugin ${metadata.model.name} already deployed`);
143
166
  return;
144
167
  }
@@ -147,14 +170,35 @@ export class HostedPluginDeployerHandler implements PluginDeployerHandler {
147
170
  const deployed: DeployedPlugin = { metadata, type };
148
171
  deployed.contributes = this.reader.readContribution(manifest);
149
172
  this.localizationService.deployLocalizations(deployed);
150
- deployedPlugins.set(metadata.model.id, deployed);
151
- deployPlugin.log(`Deployed ${entryPoint} plugin "${metadata.model.name}@${metadata.model.version}" from "${metadata.model.entryPoint[entryPoint] || pluginPath}"`);
173
+ deployedPlugins.set(id, deployed);
174
+ deployPlugin.log(`Deployed ${entryPoint} plugin "${id}" from "${metadata.model.entryPoint[entryPoint] || pluginPath}"`);
152
175
  } catch (e) {
176
+ success = false;
153
177
  deployPlugin.error(`Failed to deploy ${entryPoint} plugin from '${pluginPath}' path`, e);
178
+ } finally {
179
+ if (success && id) {
180
+ this.uninstallationManager.markAsInstalled(id);
181
+ }
182
+ }
183
+ }
184
+
185
+ async uninstallPlugin(pluginId: PluginIdentifiers.VersionedId): Promise<boolean> {
186
+ try {
187
+ const originalPath = this.originalLocations.get(pluginId);
188
+ if (!originalPath) {
189
+ return false;
190
+ }
191
+ await fs.remove(originalPath);
192
+ this.originalLocations.delete(pluginId);
193
+ this.uninstallationManager.markAsUninstalled(pluginId);
194
+ return true;
195
+ } catch (e) {
196
+ console.error('Error uninstalling plugin', e);
197
+ return false;
154
198
  }
155
199
  }
156
200
 
157
- async undeployPlugin(pluginId: string): Promise<boolean> {
201
+ async undeployPlugin(pluginId: PluginIdentifiers.VersionedId): Promise<boolean> {
158
202
  this.deployedBackendPlugins.delete(pluginId);
159
203
  this.deployedFrontendPlugins.delete(pluginId);
160
204
  const deployedLocations = this.deployedLocations.get(pluginId);
@@ -18,7 +18,7 @@ import * as cp from 'child_process';
18
18
  import { injectable, inject, named } from '@theia/core/shared/inversify';
19
19
  import { ILogger, ConnectionErrorHandler, ContributionProvider, MessageService } from '@theia/core/lib/common';
20
20
  import { createIpcEnv } from '@theia/core/lib/node/messaging/ipc-protocol';
21
- import { HostedPluginClient, ServerPluginRunner, PluginHostEnvironmentVariable, DeployedPlugin, PLUGIN_HOST_BACKEND } from '../../common/plugin-protocol';
21
+ import { HostedPluginClient, ServerPluginRunner, PluginHostEnvironmentVariable, DeployedPlugin, PLUGIN_HOST_BACKEND, PluginIdentifiers } from '../../common/plugin-protocol';
22
22
  import { MessageType } from '../../common/rpc-protocol';
23
23
  import { HostedPluginCliContribution } from './hosted-plugin-cli-contribution';
24
24
  import * as psTree from 'ps-tree';
@@ -225,7 +225,7 @@ export class HostedPluginProcess implements ServerPluginRunner {
225
225
  /**
226
226
  * Provides additional plugin ids.
227
227
  */
228
- public async getExtraDeployedPluginIds(): Promise<string[]> {
228
+ public async getExtraDeployedPluginIds(): Promise<PluginIdentifiers.VersionedId[]> {
229
229
  return [];
230
230
  }
231
231
 
@@ -16,7 +16,7 @@
16
16
 
17
17
  import { injectable, inject, multiInject, postConstruct, optional } from '@theia/core/shared/inversify';
18
18
  import { ILogger, ConnectionErrorHandler } from '@theia/core/lib/common';
19
- import { HostedPluginClient, PluginModel, ServerPluginRunner, DeployedPlugin } from '../../common/plugin-protocol';
19
+ import { HostedPluginClient, PluginModel, ServerPluginRunner, DeployedPlugin, PluginIdentifiers } from '../../common/plugin-protocol';
20
20
  import { LogPart } from '../../common/types';
21
21
  import { HostedPluginProcess } from './hosted-plugin-process';
22
22
 
@@ -95,7 +95,7 @@ export class HostedPluginSupport {
95
95
  /**
96
96
  * Provides additional plugin ids.
97
97
  */
98
- async getExtraDeployedPluginIds(): Promise<string[]> {
98
+ async getExtraDeployedPluginIds(): Promise<PluginIdentifiers.VersionedId[]> {
99
99
  return [].concat.apply([], await Promise.all(this.pluginRunners.map(runner => runner.getExtraDeployedPluginIds())));
100
100
  }
101
101
 
@@ -14,15 +14,16 @@
14
14
  // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
15
  // *****************************************************************************
16
16
 
17
- import { injectable, multiInject } from '@theia/core/shared/inversify';
18
- import { PluginPackage, PluginScanner, PluginMetadata, PLUGIN_HOST_BACKEND } from '../../common/plugin-protocol';
17
+ import { inject, injectable, multiInject } from '@theia/core/shared/inversify';
18
+ import { PluginPackage, PluginScanner, PluginMetadata, PLUGIN_HOST_BACKEND, PluginIdentifiers } from '../../common/plugin-protocol';
19
+ import { PluginUninstallationManager } from '../../main/node/plugin-uninstallation-manager';
19
20
  @injectable()
20
21
  export class MetadataScanner {
21
22
  private scanners: Map<string, PluginScanner> = new Map();
22
23
 
23
- constructor( // eslint-disable-next-line @typescript-eslint/indent
24
- @multiInject(PluginScanner) scanners: PluginScanner[]
25
- ) {
24
+ @inject(PluginUninstallationManager) protected readonly uninstallationManager: PluginUninstallationManager;
25
+
26
+ constructor(@multiInject(PluginScanner) scanners: PluginScanner[]) {
26
27
  scanners.forEach((scanner: PluginScanner) => {
27
28
  this.scanners.set(scanner.apiType, scanner);
28
29
  });
@@ -33,7 +34,8 @@ export class MetadataScanner {
33
34
  return {
34
35
  host: PLUGIN_HOST_BACKEND,
35
36
  model: scanner.getModel(plugin),
36
- lifecycle: scanner.getLifecycle(plugin)
37
+ lifecycle: scanner.getLifecycle(plugin),
38
+ outOfSync: this.uninstallationManager.isUninstalled(PluginIdentifiers.componentsToVersionedId(plugin)),
37
39
  };
38
40
  }
39
41
 
@@ -16,6 +16,7 @@
16
16
 
17
17
  import * as path from 'path';
18
18
  import * as fs from '@theia/core/shared/fs-extra';
19
+ import { PluginIdentifiers } from '../../common';
19
20
 
20
21
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
21
22
  export async function loadManifest(pluginPath: string): Promise<any> {
@@ -25,5 +26,6 @@ export async function loadManifest(pluginPath: string): Promise<any> {
25
26
  if (manifest && manifest.name && manifest.name.startsWith(built_prefix)) {
26
27
  manifest.name = manifest.name.substr(built_prefix.length);
27
28
  }
29
+ manifest.publisher ??= PluginIdentifiers.UNPUBLISHED;
28
30
  return manifest;
29
31
  }
@@ -14,13 +14,14 @@
14
14
  // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
15
  // *****************************************************************************
16
16
  import { injectable, inject, named, postConstruct } from '@theia/core/shared/inversify';
17
- import { HostedPluginServer, HostedPluginClient, PluginDeployer, GetDeployedPluginsParams, DeployedPlugin } from '../../common/plugin-protocol';
17
+ import { HostedPluginServer, HostedPluginClient, PluginDeployer, GetDeployedPluginsParams, DeployedPlugin, PluginIdentifiers } from '../../common/plugin-protocol';
18
18
  import { HostedPluginSupport } from './hosted-plugin';
19
- import { ILogger, Disposable, ContributionProvider } from '@theia/core';
19
+ import { ILogger, Disposable, ContributionProvider, DisposableCollection } from '@theia/core';
20
20
  import { ExtPluginApiProvider, ExtPluginApi } from '../../common/plugin-ext-api-contribution';
21
21
  import { HostedPluginDeployerHandler } from './hosted-plugin-deployer-handler';
22
22
  import { PluginDeployerImpl } from '../../main/node/plugin-deployer-impl';
23
23
  import { HostedPluginLocalizationService } from './hosted-plugin-localization-service';
24
+ import { PluginUninstallationManager } from '../../main/node/plugin-uninstallation-manager';
24
25
 
25
26
  @injectable()
26
27
  export class HostedPluginServerImpl implements HostedPluginServer {
@@ -40,9 +41,21 @@ export class HostedPluginServerImpl implements HostedPluginServer {
40
41
  @named(Symbol.for(ExtPluginApiProvider))
41
42
  protected readonly extPluginAPIContributions: ContributionProvider<ExtPluginApiProvider>;
42
43
 
44
+ @inject(PluginUninstallationManager) protected readonly uninstallationManager: PluginUninstallationManager;
45
+
43
46
  protected client: HostedPluginClient | undefined;
47
+ protected toDispose = new DisposableCollection();
48
+
49
+ protected _ignoredPlugins?: Set<PluginIdentifiers.VersionedId>;
50
+ // We ignore any plugins that are marked as uninstalled the first time the frontend requests information about deployed plugins.
51
+ protected get ignoredPlugins(): Set<PluginIdentifiers.VersionedId> {
52
+ if (!this._ignoredPlugins) {
53
+ this._ignoredPlugins = new Set(this.uninstallationManager.getUninstalledPluginIds());
54
+ }
55
+ return this._ignoredPlugins;
56
+ }
44
57
 
45
- protected deployedListener: Disposable;
58
+ protected readonly pluginVersions = new Map<PluginIdentifiers.UnversionedId, string>();
46
59
 
47
60
  constructor(
48
61
  @inject(HostedPluginSupport) private readonly hostedPlugin: HostedPluginSupport) {
@@ -50,38 +63,78 @@ export class HostedPluginServerImpl implements HostedPluginServer {
50
63
 
51
64
  @postConstruct()
52
65
  protected init(): void {
53
- this.deployedListener = this.pluginDeployer.onDidDeploy(() => {
54
- if (this.client) {
55
- this.client.onDidDeploy();
56
- }
57
- });
66
+ this.toDispose.pushAll([
67
+ this.pluginDeployer.onDidDeploy(() => this.client?.onDidDeploy()),
68
+ this.uninstallationManager.onDidChangeUninstalledPlugins(currentUninstalled => {
69
+ if (this._ignoredPlugins) {
70
+ const uninstalled = new Set(currentUninstalled);
71
+ for (const previouslyUninstalled of this._ignoredPlugins) {
72
+ if (!uninstalled.has(previouslyUninstalled)) {
73
+ this._ignoredPlugins.delete(previouslyUninstalled);
74
+ }
75
+ }
76
+ }
77
+ this.client?.onDidDeploy();
78
+ }),
79
+ Disposable.create(() => this.hostedPlugin.clientClosed()),
80
+ ]);
58
81
  }
59
82
 
60
83
  dispose(): void {
61
- this.hostedPlugin.clientClosed();
62
- this.deployedListener.dispose();
84
+ this.toDispose.dispose();
63
85
  }
86
+
64
87
  setClient(client: HostedPluginClient): void {
65
88
  this.client = client;
66
89
  this.hostedPlugin.setClient(client);
67
90
  }
68
91
 
69
- async getDeployedPluginIds(): Promise<string[]> {
92
+ async getDeployedPluginIds(): Promise<PluginIdentifiers.VersionedId[]> {
70
93
  const backendMetadata = await this.deployerHandler.getDeployedBackendPluginIds();
71
94
  if (backendMetadata.length > 0) {
72
95
  this.hostedPlugin.runPluginServer();
73
96
  }
74
- const plugins = new Set<string>();
75
- for (const pluginId of await this.deployerHandler.getDeployedFrontendPluginIds()) {
76
- plugins.add(pluginId);
97
+ const plugins = new Set<PluginIdentifiers.VersionedId>();
98
+ const addIds = async (identifiers: PluginIdentifiers.VersionedId[]): Promise<void> => {
99
+ for (const pluginId of identifiers) {
100
+ if (this.isRelevantPlugin(pluginId)) {
101
+ plugins.add(pluginId);
102
+ }
103
+ }
104
+ };
105
+ addIds(await this.deployerHandler.getDeployedFrontendPluginIds());
106
+ addIds(backendMetadata);
107
+ addIds(await this.hostedPlugin.getExtraDeployedPluginIds());
108
+ return Array.from(plugins);
109
+ }
110
+
111
+ /**
112
+ * Ensures that the plugin was not uninstalled when this session was started
113
+ * and that it matches the first version of the given plugin seen by this session.
114
+ *
115
+ * The deployment system may have multiple versions of the same plugin available, but
116
+ * a single session should only ever activate one of them.
117
+ */
118
+ protected isRelevantPlugin(identifier: PluginIdentifiers.VersionedId): boolean {
119
+ const versionAndId = PluginIdentifiers.idAndVersionFromVersionedId(identifier);
120
+ if (!versionAndId) {
121
+ return false;
77
122
  }
78
- for (const pluginId of backendMetadata) {
79
- plugins.add(pluginId);
123
+ const knownVersion = this.pluginVersions.get(versionAndId.id);
124
+ if (knownVersion !== undefined && knownVersion !== versionAndId.version) {
125
+ return false;
80
126
  }
81
- for (const pluginId of await this.hostedPlugin.getExtraDeployedPluginIds()) {
82
- plugins.add(pluginId);
127
+ if (this.ignoredPlugins.has(identifier)) {
128
+ return false;
83
129
  }
84
- return [...plugins.values()];
130
+ if (knownVersion === undefined) {
131
+ this.pluginVersions.set(versionAndId.id, versionAndId.version);
132
+ }
133
+ return true;
134
+ }
135
+
136
+ getUninstalledPluginIds(): Promise<readonly PluginIdentifiers.VersionedId[]> {
137
+ return Promise.resolve(this.uninstallationManager.getUninstalledPluginIds());
85
138
  }
86
139
 
87
140
  async getDeployedPlugins({ pluginIds }: GetDeployedPluginsParams): Promise<DeployedPlugin[]> {
@@ -90,16 +143,19 @@ export class HostedPluginServerImpl implements HostedPluginServer {
90
143
  }
91
144
  const plugins: DeployedPlugin[] = [];
92
145
  let extraDeployedPlugins: Map<string, DeployedPlugin> | undefined;
93
- for (const pluginId of pluginIds) {
94
- let plugin = this.deployerHandler.getDeployedPlugin(pluginId);
146
+ for (const versionedId of pluginIds) {
147
+ if (!this.isRelevantPlugin(versionedId)) {
148
+ continue;
149
+ }
150
+ let plugin = this.deployerHandler.getDeployedPlugin(versionedId);
95
151
  if (!plugin) {
96
152
  if (!extraDeployedPlugins) {
97
153
  extraDeployedPlugins = new Map<string, DeployedPlugin>();
98
154
  for (const extraDeployedPlugin of await this.hostedPlugin.getExtraDeployedPlugins()) {
99
- extraDeployedPlugins.set(extraDeployedPlugin.metadata.model.id, extraDeployedPlugin);
155
+ extraDeployedPlugins.set(PluginIdentifiers.componentsToVersionedId(extraDeployedPlugin.metadata.model), extraDeployedPlugin);
100
156
  }
101
157
  }
102
- plugin = extraDeployedPlugins.get(pluginId);
158
+ plugin = extraDeployedPlugins.get(versionedId);
103
159
  }
104
160
  if (plugin) {
105
161
  plugins.push(plugin);
@@ -58,7 +58,8 @@ import {
58
58
  PluginPackageLocalization,
59
59
  Localization,
60
60
  PluginPackageTranslation,
61
- Translation
61
+ Translation,
62
+ PluginIdentifiers
62
63
  } from '../../../common/plugin-protocol';
63
64
  import * as fs from 'fs';
64
65
  import * as path from 'path';
@@ -109,13 +110,14 @@ export class TheiaPluginScanner implements PluginScanner {
109
110
  }
110
111
 
111
112
  getModel(plugin: PluginPackage): PluginModel {
113
+ const publisher = plugin.publisher ?? PluginIdentifiers.UNPUBLISHED;
112
114
  const result: PluginModel = {
113
115
  packagePath: plugin.packagePath,
114
116
  packageUri: this.pluginUriFactory.createUri(plugin).toString(),
115
117
  // see id definition: https://github.com/microsoft/vscode/blob/15916055fe0cb9411a5f36119b3b012458fe0a1d/src/vs/platform/extensions/common/extensions.ts#L167-L169
116
- id: `${plugin.publisher.toLowerCase()}.${plugin.name.toLowerCase()}`,
118
+ id: `${publisher.toLowerCase()}.${plugin.name.toLowerCase()}`,
117
119
  name: plugin.name,
118
- publisher: plugin.publisher,
120
+ publisher,
119
121
  version: plugin.version,
120
122
  displayName: plugin.displayName,
121
123
  description: plugin.description,