diff --git a/CHANGELOG.md b/CHANGELOG.md index 2dbc881f452a9..933afb251a78a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ - [terminal] Use application shell methods for expanding/collapsing bottom panel for "Terminal: Toggle Terminal" command [#13131](https://github.com/eclipse-theia/theia/pull/13131) - [workspace] Create an empty workspace if no workspace is active on updateWorkspaceFolders [#13181](https://github.com/eclipse-theia/theia/pull/13181) - contributed on behalf of STMicroelectronics +[Breaking Changes:](#breaking_changes_1.45.0) + +- [plugin] handling of vscode extension locations has changed: deployment dir switched to `$CONFDIR/deployedPlugin`, `.vsix` files from `$CONFDIR/extensions` are deployed automatically [#13178](https://github.com/eclipse-theia/theia/pull/13178) - Contributed on behalf of STMicroelectronics + ## v1.44.0 - 11/30/2023 - [application-manager] added option to copy `trash` dependency to the bundle [#13112](https://github.com/eclipse-theia/theia/pull/13112) diff --git a/packages/plugin-ext-vscode/package.json b/packages/plugin-ext-vscode/package.json index 7451191db6c63..5723f802ce95d 100644 --- a/packages/plugin-ext-vscode/package.json +++ b/packages/plugin-ext-vscode/package.json @@ -16,6 +16,7 @@ "@theia/typehierarchy": "1.44.0", "@theia/userstorage": "1.44.0", "@theia/workspace": "1.44.0", + "decompress": "^4.2.1", "filenamify": "^4.1.0" }, "publishConfig": { diff --git a/packages/plugin-ext-vscode/src/common/plugin-vscode-environment.ts b/packages/plugin-ext-vscode/src/common/plugin-vscode-environment.ts index 16601eac78174..6e534f81c245b 100644 --- a/packages/plugin-ext-vscode/src/common/plugin-vscode-environment.ts +++ b/packages/plugin-ext-vscode/src/common/plugin-vscode-environment.ts @@ -24,13 +24,36 @@ export class PluginVSCodeEnvironment { @inject(EnvVariablesServer) protected readonly environments: EnvVariablesServer; - protected _extensionsDirUri: URI | undefined; - async getExtensionsDirUri(): Promise { - if (!this._extensionsDirUri) { + protected _userExtensionsDirUri: URI | undefined; + protected _deployedPluginsUri: URI | undefined; + protected _tmpDirUri: URI | undefined; + + async getUserExtensionsDirUri(): Promise { + if (!this._userExtensionsDirUri) { + const configDir = new URI(await this.environments.getConfigDirUri()); + this._userExtensionsDirUri = configDir.resolve('extensions'); + } + return this._userExtensionsDirUri; + } + + async getDeploymentDirUri(): Promise { + if (!this._deployedPluginsUri) { const configDir = new URI(await this.environments.getConfigDirUri()); - this._extensionsDirUri = configDir.resolve('extensions'); + this._deployedPluginsUri = configDir.resolve('deployedPlugins'); } - return this._extensionsDirUri; + return this._deployedPluginsUri; } + async getTempDirUri(prefix?: string): Promise { + if (!this._tmpDirUri) { + const configDir: URI = new URI(await this.environments.getConfigDirUri()); + this._tmpDirUri = configDir.resolve('tmp'); + } + + if (prefix) { + return this._tmpDirUri.resolve(prefix); + } + + return this._tmpDirUri; + } } diff --git a/packages/plugin-ext-vscode/src/node/local-vsix-file-plugin-deployer-resolver.ts b/packages/plugin-ext-vscode/src/node/local-vsix-file-plugin-deployer-resolver.ts index a151ea26395bf..5de67f2206fbb 100644 --- a/packages/plugin-ext-vscode/src/node/local-vsix-file-plugin-deployer-resolver.ts +++ b/packages/plugin-ext-vscode/src/node/local-vsix-file-plugin-deployer-resolver.ts @@ -14,18 +14,18 @@ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** -import * as fs from '@theia/core/shared/fs-extra'; import * as path from 'path'; import { inject, injectable } from '@theia/core/shared/inversify'; -import { FileUri } from '@theia/core/lib/node'; import { PluginDeployerResolverContext } from '@theia/plugin-ext'; import { LocalPluginDeployerResolver } from '@theia/plugin-ext/lib/main/node/resolvers/local-plugin-deployer-resolver'; import { PluginVSCodeEnvironment } from '../common/plugin-vscode-environment'; import { isVSCodePluginFile } from './plugin-vscode-file-handler'; +import { existsInDeploymentDir, unpackToDeploymentDir } from './plugin-vscode-utils'; @injectable() export class LocalVSIXFilePluginDeployerResolver extends LocalPluginDeployerResolver { static LOCAL_FILE = 'local-file'; + static FILE_EXTENSION = '.vsix'; @inject(PluginVSCodeEnvironment) protected readonly environment: PluginVSCodeEnvironment; @@ -38,28 +38,14 @@ export class LocalVSIXFilePluginDeployerResolver extends LocalPluginDeployerReso } async resolveFromLocalPath(pluginResolverContext: PluginDeployerResolverContext, localPath: string): Promise { - const fileName = path.basename(localPath); - const pathInUserExtensionsDirectory = await this.ensureDiscoverability(localPath); - pluginResolverContext.addPlugin(fileName, pathInUserExtensionsDirectory); - } + const extensionId = path.basename(localPath, LocalVSIXFilePluginDeployerResolver.FILE_EXTENSION); - /** - * Ensures that a user-installed plugin file is transferred to the user extension folder. - */ - protected async ensureDiscoverability(localPath: string): Promise { - const userExtensionsDir = await this.environment.getExtensionsDirUri(); - if (!userExtensionsDir.isEqualOrParent(FileUri.create(localPath))) { - try { - const newPath = FileUri.fsPath(userExtensionsDir.resolve(path.basename(localPath))); - await fs.mkdirp(FileUri.fsPath(userExtensionsDir)); - await new Promise((resolve, reject) => { - fs.copyFile(localPath, newPath, error => error ? reject(error) : resolve()); - }); - return newPath; - } catch (e) { - console.warn(`Problem copying plugin at ${localPath}:`, e); - } + if (await existsInDeploymentDir(this.environment, extensionId)) { + console.log(`[${pluginResolverContext.getOriginId()}]: Target dir already exists in plugin deployment dir`); + return; } - return localPath; + + const extensionDeploymentDir = await unpackToDeploymentDir(this.environment, localPath, extensionId); + pluginResolverContext.addPlugin(extensionId, extensionDeploymentDir); } } diff --git a/packages/plugin-ext-vscode/src/node/plugin-vscode-deployer-participant.ts b/packages/plugin-ext-vscode/src/node/plugin-vscode-deployer-participant.ts index 5508feda6d79f..aefa68b3f7b5b 100644 --- a/packages/plugin-ext-vscode/src/node/plugin-vscode-deployer-participant.ts +++ b/packages/plugin-ext-vscode/src/node/plugin-vscode-deployer-participant.ts @@ -15,8 +15,11 @@ // ***************************************************************************** import { injectable, inject } from '@theia/core/shared/inversify'; +import * as fs from '@theia/core/shared/fs-extra'; +import { FileUri } from '@theia/core/lib/node'; import { PluginVSCodeEnvironment } from '../common/plugin-vscode-environment'; import { PluginDeployerParticipant, PluginDeployerStartContext } from '@theia/plugin-ext/lib/common/plugin-protocol'; +import { LocalVSIXFilePluginDeployerResolver } from './local-vsix-file-plugin-deployer-resolver'; @injectable() export class PluginVSCodeDeployerParticipant implements PluginDeployerParticipant { @@ -25,8 +28,21 @@ export class PluginVSCodeDeployerParticipant implements PluginDeployerParticipan protected readonly environments: PluginVSCodeEnvironment; async onWillStart(context: PluginDeployerStartContext): Promise { - const extensionsDirUri = await this.environments.getExtensionsDirUri(); - context.userEntries.push(extensionsDirUri.withScheme('local-dir').toString()); - } + const extensionDeploymentDirUri = await this.environments.getDeploymentDirUri(); + context.userEntries.push(extensionDeploymentDirUri.withScheme('local-dir').toString()); + + const userExtensionDirUri = await this.environments.getUserExtensionsDirUri(); + const userExtensionDirPath = FileUri.fsPath(userExtensionDirUri); + if (await fs.pathExists(userExtensionDirPath)) { + const files = await fs.readdir(userExtensionDirPath); + for (const file of files) { + if (file.endsWith(LocalVSIXFilePluginDeployerResolver.FILE_EXTENSION)) { + const extensionUri = userExtensionDirUri.resolve(file).withScheme('local-file').toString(); + console.log(`found drop-in extension "${extensionUri}"`); + context.userEntries.push(extensionUri); + } + } + } + } } diff --git a/packages/plugin-ext-vscode/src/node/plugin-vscode-directory-handler.ts b/packages/plugin-ext-vscode/src/node/plugin-vscode-directory-handler.ts index f135aead0413e..bcbce5d3fc0cb 100644 --- a/packages/plugin-ext-vscode/src/node/plugin-vscode-directory-handler.ts +++ b/packages/plugin-ext-vscode/src/node/plugin-vscode-directory-handler.ts @@ -15,18 +15,16 @@ // ***************************************************************************** import * as path from 'path'; -import * as filenamify from 'filenamify'; import * as fs from '@theia/core/shared/fs-extra'; import { inject, injectable } from '@theia/core/shared/inversify'; import type { RecursivePartial, URI } from '@theia/core'; import { Deferred, firstTrue } from '@theia/core/lib/common/promise-util'; -import { getTempDirPathAsync } from '@theia/plugin-ext/lib/main/node/temp-dir-util'; import { PluginDeployerDirectoryHandler, PluginDeployerEntry, PluginDeployerDirectoryHandlerContext, - PluginDeployerEntryType, PluginPackage, PluginType, PluginIdentifiers + PluginDeployerEntryType, PluginPackage, PluginIdentifiers } from '@theia/plugin-ext'; -import { FileUri } from '@theia/core/lib/node'; import { PluginCliContribution } from '@theia/plugin-ext/lib/main/node/plugin-cli-contribution'; +import { TMP_DIR_PREFIX } from './plugin-vscode-utils'; @injectable() export class PluginVsCodeDirectoryHandler implements PluginDeployerDirectoryHandler { @@ -35,14 +33,12 @@ export class PluginVsCodeDirectoryHandler implements PluginDeployerDirectoryHand @inject(PluginCliContribution) protected readonly pluginCli: PluginCliContribution; - constructor() { - this.deploymentDirectory = new Deferred(); - getTempDirPathAsync('vscode-copied') - .then(deploymentDirectoryPath => this.deploymentDirectory.resolve(FileUri.create(deploymentDirectoryPath))); - } - async accept(plugin: PluginDeployerEntry): Promise { console.debug(`Resolving "${plugin.id()}" as a VS Code extension...`); + if (plugin.path().startsWith(TMP_DIR_PREFIX)) { + // avoid adding corrupted plugins from temporary directories + return false; + } return this.attemptResolution(plugin); } @@ -62,7 +58,6 @@ export class PluginVsCodeDirectoryHandler implements PluginDeployerDirectoryHand } async handle(context: PluginDeployerDirectoryHandlerContext): Promise { - await this.copyDirectory(context); const types: PluginDeployerEntryType[] = []; const packageJson: PluginPackage = context.pluginEntry().getValue('package.json'); if (packageJson.browser) { @@ -74,33 +69,6 @@ export class PluginVsCodeDirectoryHandler implements PluginDeployerDirectoryHand context.pluginEntry().accept(...types); } - protected async copyDirectory(context: PluginDeployerDirectoryHandlerContext): Promise { - if (this.pluginCli.copyUncompressedPlugins() && context.pluginEntry().type === PluginType.User) { - const entry = context.pluginEntry(); - const id = entry.id(); - const pathToRestore = entry.path(); - const origin = entry.originalPath(); - const targetDir = await this.getExtensionDir(context); - try { - if (await fs.pathExists(targetDir) || !entry.path().startsWith(origin)) { - console.log(`[${id}]: already copied.`); - } else { - console.log(`[${id}]: copying to "${targetDir}"`); - const deploymentDirectory = await this.deploymentDirectory.promise; - await fs.mkdirp(FileUri.fsPath(deploymentDirectory)); - await context.copy(origin, targetDir); - entry.updatePath(targetDir); - if (!this.deriveMetadata(entry)) { - throw new Error('Unable to resolve plugin metadata after copying'); - } - } - } catch (e) { - console.warn(`[${id}]: Error when copying.`, e); - entry.updatePath(pathToRestore); - } - } - } - protected async resolveFromSources(plugin: PluginDeployerEntry): Promise { const pluginPath = plugin.path(); const pck = await this.requirePackage(pluginPath); @@ -152,9 +120,4 @@ export class PluginVsCodeDirectoryHandler implements PluginDeployerDirectoryHand return undefined; } } - - protected async getExtensionDir(context: PluginDeployerDirectoryHandlerContext): Promise { - const deploymentDirectory = await this.deploymentDirectory.promise; - return FileUri.fsPath(deploymentDirectory.resolve(filenamify(context.pluginEntry().id(), { replacement: '_' }))); - } } diff --git a/packages/plugin-ext-vscode/src/node/plugin-vscode-file-handler.ts b/packages/plugin-ext-vscode/src/node/plugin-vscode-file-handler.ts index 23e3e3e9292d3..f3f6459aa95cc 100644 --- a/packages/plugin-ext-vscode/src/node/plugin-vscode-file-handler.ts +++ b/packages/plugin-ext-vscode/src/node/plugin-vscode-file-handler.ts @@ -14,33 +14,21 @@ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** -import { PluginDeployerFileHandler, PluginDeployerEntry, PluginDeployerFileHandlerContext, PluginType } from '@theia/plugin-ext'; -import * as fs from '@theia/core/shared/fs-extra'; -import * as path from 'path'; +import { PluginDeployerFileHandler, PluginDeployerEntry, PluginDeployerFileHandlerContext } from '@theia/plugin-ext'; import * as filenamify from 'filenamify'; -import type { URI } from '@theia/core'; import { inject, injectable } from '@theia/core/shared/inversify'; -import { Deferred } from '@theia/core/lib/common/promise-util'; -import { getTempDirPathAsync } from '@theia/plugin-ext/lib/main/node/temp-dir-util'; +import * as fs from '@theia/core/shared/fs-extra'; +import { FileUri } from '@theia/core/lib/node'; import { PluginVSCodeEnvironment } from '../common/plugin-vscode-environment'; -import { FileUri } from '@theia/core/lib/node/file-uri'; +import { unpackToDeploymentDir } from './plugin-vscode-utils'; export const isVSCodePluginFile = (pluginPath?: string) => Boolean(pluginPath && (pluginPath.endsWith('.vsix') || pluginPath.endsWith('.tgz'))); @injectable() export class PluginVsCodeFileHandler implements PluginDeployerFileHandler { - @inject(PluginVSCodeEnvironment) protected readonly environment: PluginVSCodeEnvironment; - private readonly systemExtensionsDirUri: Deferred; - - constructor() { - this.systemExtensionsDirUri = new Deferred(); - getTempDirPathAsync('vscode-unpacked') - .then(systemExtensionsDirPath => this.systemExtensionsDirUri.resolve(FileUri.create(systemExtensionsDirPath))); - } - async accept(resolvedPlugin: PluginDeployerEntry): Promise { return resolvedPlugin.isFile().then(file => { if (!file) { @@ -51,33 +39,24 @@ export class PluginVsCodeFileHandler implements PluginDeployerFileHandler { } async handle(context: PluginDeployerFileHandlerContext): Promise { - const id = context.pluginEntry().id(); - const extensionDir = await this.getExtensionDir(context); - console.log(`[${id}]: trying to decompress into "${extensionDir}"...`); - if (context.pluginEntry().type === PluginType.User && await fs.pathExists(extensionDir)) { - console.log(`[${id}]: already found`); - context.pluginEntry().updatePath(extensionDir); - return; - } - await this.decompress(extensionDir, context); - console.log(`[${id}]: decompressed`); - context.pluginEntry().updatePath(extensionDir); - } - - protected async getExtensionDir(context: PluginDeployerFileHandlerContext): Promise { - const systemExtensionsDirUri = await this.systemExtensionsDirUri.promise; - return FileUri.fsPath(systemExtensionsDirUri.resolve(filenamify(context.pluginEntry().id(), { replacement: '_' }))); - } - - protected async decompress(extensionDir: string, context: PluginDeployerFileHandlerContext): Promise { - await context.unzip(context.pluginEntry().path(), extensionDir); - if (context.pluginEntry().path().endsWith('.tgz')) { - const extensionPath = path.join(extensionDir, 'package'); - const vscodeNodeModulesPath = path.join(extensionPath, 'vscode_node_modules.zip'); - if (await fs.pathExists(vscodeNodeModulesPath)) { - await context.unzip(vscodeNodeModulesPath, path.join(extensionPath, 'node_modules')); + const id = this.getNormalizedExtensionId(context.pluginEntry().id()); + const extensionDeploymentDir = await unpackToDeploymentDir(this.environment, context.pluginEntry().path(), id); + context.pluginEntry().updatePath(extensionDeploymentDir); + console.log(`root path: ${context.pluginEntry().rootPath}`); + const originalPath = context.pluginEntry().originalPath(); + if (originalPath && originalPath !== extensionDeploymentDir) { + const tempDirUri = await this.environment.getTempDirUri(); + if (originalPath.startsWith(FileUri.fsPath(tempDirUri))) { + try { + await fs.remove(FileUri.fsPath(originalPath)); + } catch (e) { + console.error(`[${id}]: failed to remove temporary files: "${originalPath}"`, e); + } } } } + protected getNormalizedExtensionId(pluginId: string): string { + return filenamify(pluginId, { replacement: '_' }).replace(/\.vsix$/, ''); + } } diff --git a/packages/plugin-ext-vscode/src/node/plugin-vscode-utils.ts b/packages/plugin-ext-vscode/src/node/plugin-vscode-utils.ts new file mode 100644 index 0000000000000..6f6b7d6c6d60c --- /dev/null +++ b/packages/plugin-ext-vscode/src/node/plugin-vscode-utils.ts @@ -0,0 +1,101 @@ +// ***************************************************************************** +// Copyright (C) 2023 STMicroelectronics and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0. +// +// This Source Code may also be made available under the following Secondary +// Licenses when the conditions for such availability set forth in the Eclipse +// Public License v. 2.0 are satisfied: GNU General Public License, version 2 +// with the GNU Classpath Exception which is available at +// https://www.gnu.org/software/classpath/license.html. +// +// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 +// ***************************************************************************** + +import * as decompress from 'decompress'; +import * as path from 'path'; +import * as filenamify from 'filenamify'; +import { FileUri } from '@theia/core/lib/node'; +import * as fs from '@theia/core/shared/fs-extra'; +import { PluginVSCodeEnvironment } from '../common/plugin-vscode-environment'; + +export async function decompressExtension(sourcePath: string, destPath: string): Promise { + try { + await decompress(sourcePath, destPath); + if (sourcePath.endsWith('.tgz')) { + // unzip node_modules from built-in extensions, see https://github.com/eclipse-theia/theia/issues/5756 + const extensionPath = path.join(destPath, 'package'); + const vscodeNodeModulesPath = path.join(extensionPath, 'vscode_node_modules.zip'); + if (await fs.pathExists(vscodeNodeModulesPath)) { + await decompress(vscodeNodeModulesPath, path.join(extensionPath, 'node_modules')); + } + } + return true; + } catch (error) { + console.error(`Failed to decompress ${sourcePath} to ${destPath}: ${error}`); + throw error; + } +} + +export async function existsInDeploymentDir(env: PluginVSCodeEnvironment, extensionId: string): Promise { + return fs.pathExists(await getExtensionDeploymentDir(env, extensionId)); +} + +export const TMP_DIR_PREFIX = 'tmp-vscode-unpacked-'; +export async function unpackToDeploymentDir(env: PluginVSCodeEnvironment, sourcePath: string, extensionId: string): Promise { + const extensionDeploymentDir = await getExtensionDeploymentDir(env, extensionId); + if (await fs.pathExists(extensionDeploymentDir)) { + console.log(`[${extensionId}]: deployment dir "${extensionDeploymentDir}" already exists`); + return extensionDeploymentDir; + } + + const tempDir = await getTempDir(env, TMP_DIR_PREFIX); + try { + console.log(`[${extensionId}]: trying to decompress "${sourcePath}" into "${tempDir}"...`); + if (!await decompressExtension(sourcePath, tempDir)) { + await fs.remove(tempDir); + const msg = `[${extensionId}]: decompressing "${sourcePath}" to "${tempDir}" failed`; + console.error(msg); + throw new Error(msg); + } + } catch (e) { + await fs.remove(tempDir); + const msg = `[${extensionId}]: error while decompressing "${sourcePath}" to "${tempDir}"`; + console.error(msg, e); + throw e; + } + console.log(`[${extensionId}]: decompressed to temp dir "${tempDir}"`); + + try { + console.log(`[${extensionId}]: renaming to extension dir "${extensionDeploymentDir}"...`); + await fs.rename(tempDir, extensionDeploymentDir); + return extensionDeploymentDir; + } catch (e) { + await fs.remove(tempDir); + console.error(`[${extensionId}]: error while renaming "${tempDir}" to "${extensionDeploymentDir}"`, e); + throw e; + } +} + +export async function getExtensionDeploymentDir(env: PluginVSCodeEnvironment, extensionId: string): Promise { + const deployedPluginsDirUri = await env.getDeploymentDirUri(); + const normalizedExtensionId = filenamify(extensionId, { replacement: '_' }); + const extensionDeploymentDirPath = FileUri.fsPath(deployedPluginsDirUri.resolve(normalizedExtensionId)); + return extensionDeploymentDirPath; +} + +export async function getTempDir(env: PluginVSCodeEnvironment, prefix: string): Promise { + const deploymentDirPath = FileUri.fsPath(await env.getDeploymentDirUri()); + try { + if (!await fs.pathExists(deploymentDirPath)) { + console.log(`Creating deployment dir ${deploymentDirPath}`); + await fs.mkdirs(deploymentDirPath); + } + return await fs.mkdtemp(path.join(deploymentDirPath, prefix)); + } catch (error) { + console.error(`Failed to create deployment dir ${deploymentDirPath}: ${error}`); + throw error; + } +} diff --git a/packages/plugin-ext/src/main/node/plugin-deployer-contribution.ts b/packages/plugin-ext/src/main/node/plugin-deployer-contribution.ts index d5c9f38d7aab9..ffc6dafc83ca0 100644 --- a/packages/plugin-ext/src/main/node/plugin-deployer-contribution.ts +++ b/packages/plugin-ext/src/main/node/plugin-deployer-contribution.ts @@ -28,7 +28,8 @@ export class PluginDeployerContribution implements BackendApplicationContributio @inject(PluginDeployer) protected pluginDeployer: PluginDeployer; - initialize(): void { + initialize(): Promise { this.pluginDeployer.start().catch(error => this.logger.error('Initializing plugin deployer failed.', error)); + return Promise.resolve(); } } diff --git a/packages/vsx-registry/src/node/vsx-extension-resolver.ts b/packages/vsx-registry/src/node/vsx-extension-resolver.ts index cfe6ab3d09369..cef7c940918a7 100644 --- a/packages/vsx-registry/src/node/vsx-extension-resolver.ts +++ b/packages/vsx-registry/src/node/vsx-extension-resolver.ts @@ -20,6 +20,7 @@ import * as fs from '@theia/core/shared/fs-extra'; import { injectable, inject } from '@theia/core/shared/inversify'; import URI from '@theia/core/lib/common/uri'; import { PluginDeployerHandler, PluginDeployerResolver, PluginDeployerResolverContext, PluginDeployOptions, PluginIdentifiers } from '@theia/plugin-ext/lib/common/plugin-protocol'; +import { FileUri } from '@theia/core/lib/node'; import { VSCodeExtensionUri } from '@theia/plugin-ext-vscode/lib/common/plugin-vscode-uri'; import { OVSXClientProvider } from '../common/ovsx-client-provider'; import { OVSXApiFilter, VSXExtensionRaw } from '@theia/ovsx-client'; @@ -41,6 +42,8 @@ export class VSXExtensionResolver implements PluginDeployerResolver { return !!VSCodeExtensionUri.toId(new URI(pluginId)); } + static readonly TEMP_DIR_PREFIX = 'vscode-download'; + async resolve(context: PluginDeployerResolverContext, options?: PluginDeployOptions): Promise { const id = VSCodeExtensionUri.toId(new URI(context.getOriginId())); if (!id) { @@ -74,16 +77,24 @@ export class VSXExtensionResolver implements PluginDeployerResolver { return; } } - const downloadPath = (await this.environment.getExtensionsDirUri()).path.fsPath(); - await fs.ensureDir(downloadPath); - const extensionPath = path.resolve(downloadPath, path.basename(downloadUrl)); - console.log(`[${resolvedId}]: trying to download from "${downloadUrl}"...`, 'to path', downloadPath); - if (!await this.download(downloadUrl, extensionPath)) { + const downloadDir = await this.getTempDir(); + await fs.ensureDir(downloadDir); + const downloadedExtensionPath = path.resolve(downloadDir, path.basename(downloadUrl)); + console.log(`[${resolvedId}]: trying to download from "${downloadUrl}"...`, 'to path', downloadDir); + if (!await this.download(downloadUrl, downloadedExtensionPath)) { console.log(`[${resolvedId}]: not found`); return; } - console.log(`[${resolvedId}]: downloaded to ${extensionPath}"`); - context.addPlugin(resolvedId, extensionPath); + console.log(`[${resolvedId}]: downloaded to ${downloadedExtensionPath}"`); + context.addPlugin(resolvedId, downloadedExtensionPath); + } + + protected async getTempDir(): Promise { + const tempDir = FileUri.fsPath(await this.environment.getTempDirUri(VSXExtensionResolver.TEMP_DIR_PREFIX)); + if (!await fs.pathExists(tempDir)) { + await fs.mkdirs(tempDir); + } + return tempDir; } protected hasSameOrNewerVersion(id: string, extension: VSXExtensionRaw): string | undefined {