Skip to content

Commit f2591bc

Browse files
authored
feat(amazonq): handle extension uninstallation (#5410)
Setup Uninstall Handler for extensions. - [x] Telemetry and Logger needs to be verified. ## License By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent 93e5a09 commit f2591bc

File tree

8 files changed

+120
-7
lines changed

8 files changed

+120
-7
lines changed

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
"generateNonCodeFiles": "npm run generateNonCodeFiles -w packages/ --if-present"
4040
},
4141
"devDependencies": {
42-
"@aws-toolkits/telemetry": "^1.0.250",
42+
"@aws-toolkits/telemetry": "^1.0.254",
4343
"@playwright/browser-chromium": "^1.43.1",
4444
"@types/vscode": "^1.68.0",
4545
"@types/vscode-webview": "^1.57.1",
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Feature",
3+
"description": "Record telemetry event when AWS Toolkits extension is uninstalled."
4+
}

packages/amazonq/src/extension.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import {
3939
messages,
4040
placeholder,
4141
setContext,
42+
setupUninstallHandler,
4243
} from 'aws-core-vscode/shared'
4344
import { ExtStartUpSources, telemetry } from 'aws-core-vscode/telemetry'
4445
import { VSCODE_EXTENSION_ID } from 'aws-core-vscode/utils'
@@ -129,6 +130,9 @@ export async function activateAmazonQCommon(context: vscode.ExtensionContext, is
129130
// Amazon Q specific commands
130131
registerCommands(context)
131132

133+
// Handle Amazon Q Extension un-installation.
134+
setupUninstallHandler(VSCODE_EXTENSION_ID.amazonq, context)
135+
132136
// Hide the Amazon Q tree in toolkit explorer
133137
await setContext('aws.toolkit.amazonq.dismissed', true)
134138

packages/core/src/extension.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ import { getMachineId, isAutomation } from './shared/vscode/env'
3939
import { registerCommandErrorHandler } from './shared/vscode/commands2'
4040
import { registerWebviewErrorHandler } from './webviews/server'
4141
import { showQuickStartWebview } from './shared/extensionStartup'
42-
import { ExtContext } from './shared/extensions'
42+
import { ExtContext, VSCODE_EXTENSION_ID } from './shared/extensions'
4343
import { getSamCliContext } from './shared/sam/cli/samCliContext'
4444
import { UriHandler } from './shared/vscode/uriHandler'
4545
import { disableAwsSdkWarning } from './shared/awsClientBuilder'
@@ -55,7 +55,7 @@ import { registerCommands } from './commands'
5555
// In web mode everything must be in a single file, so things like the endpoints file will not be available.
5656
// The following imports the endpoints file, which causes webpack to bundle it in the final output file
5757
import endpoints from '../resources/endpoints.json'
58-
import { getLogger } from './shared'
58+
import { getLogger, setupUninstallHandler } from './shared'
5959
import { showViewLogsMessage } from './shared/utilities/messages'
6060

6161
disableAwsSdkWarning()
@@ -154,6 +154,9 @@ export async function activateCommon(
154154
})
155155
)
156156

157+
// Handle AWS Toolkit un-installation.
158+
setupUninstallHandler(VSCODE_EXTENSION_ID.awstoolkit, context)
159+
157160
// auth
158161
await initializeAuth(globals.loginManager)
159162
await initializeAwsCredentialsStatusBarItem(globals.awsContext, context)
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
// Implementation inspired by https://github.com/sourcegraph/sourcegraph-public-snapshot/blob/c864f15af264f0f456a6d8a83290b5c940715349/client/vscode/src/settings/uninstall.ts#L2
7+
8+
import * as vscode from 'vscode'
9+
import { join } from 'path'
10+
import { getLogger } from './logger/logger'
11+
import { telemetry } from './telemetry'
12+
import { VSCODE_EXTENSION_ID } from './extensions'
13+
import { extensionVersion } from './vscode/env'
14+
15+
/**
16+
* Checks if the extension has been uninstalled by reading the .obsolete file
17+
* and comparing the number of obsolete extensions with the installed extensions.
18+
*
19+
* @param {string} extensionName - The name of the extension.
20+
* @param {string} extensionsDirPath - The path to the extensions directory.
21+
* @param {string} obsoleteFilePath - The path to the .obsolete file.
22+
* @param {function} callback - Action performed when extension is uninstalled.
23+
* @returns {void}
24+
*/
25+
async function checkExtensionUninstall(
26+
extensionName: typeof VSCODE_EXTENSION_ID.awstoolkit | typeof VSCODE_EXTENSION_ID.amazonq,
27+
extensionsDirPath: string,
28+
obsoleteFilePath: string,
29+
callback: () => Promise<void>
30+
): Promise<void> {
31+
/**
32+
* Users can have multiple profiles with different versions of the extensions.
33+
*
34+
* This makes sure the callback is triggered only when an explicit extension with specific version is uninstalled.
35+
*/
36+
const extension = `${extensionName}-${extensionVersion}`
37+
try {
38+
const [obsoleteFileContent, extensionsDirContent] = await Promise.all([
39+
vscode.workspace.fs.readFile(vscode.Uri.file(obsoleteFilePath)),
40+
vscode.workspace.fs.readDirectory(vscode.Uri.file(extensionsDirPath)),
41+
])
42+
43+
const installedExtensionsCount = extensionsDirContent
44+
.map(([name]) => name)
45+
.filter((name) => name.includes(extension)).length
46+
47+
const obsoleteExtensions = JSON.parse(obsoleteFileContent.toString())
48+
const obsoleteExtensionsCount = Object.keys(obsoleteExtensions).filter((id) => id.includes(extension)).length
49+
50+
if (installedExtensionsCount === obsoleteExtensionsCount) {
51+
await callback()
52+
telemetry.aws_extensionUninstalled.run((span) => {
53+
span.record({})
54+
})
55+
getLogger().info(`UninstallExtension: ${extension} uninstalled successfully`)
56+
}
57+
} catch (error) {
58+
getLogger().error(`UninstallExtension: Failed to check .obsolete: ${error}`)
59+
}
60+
}
61+
62+
/**
63+
* Sets up a file system watcher to monitor the .obsolete file for changes and handle
64+
* extension un-installation if the extension is marked as obsolete.
65+
*
66+
* @param {string} extensionName - The name of the extension.
67+
* @param {vscode.ExtensionContext} context - The extension context.
68+
* @param {function} callback - Action performed when extension is uninstalled.
69+
* @returns {void}
70+
*/
71+
export function setupUninstallHandler(
72+
extensionName: typeof VSCODE_EXTENSION_ID.awstoolkit | typeof VSCODE_EXTENSION_ID.amazonq,
73+
context: vscode.ExtensionContext,
74+
callback: () => Promise<void> = async () => {}
75+
): void {
76+
try {
77+
const extensionPath = context.extensionPath
78+
const pathComponents = extensionPath.split('/').slice(0, -1)
79+
const extensionsDirPath = pathComponents.join('/')
80+
81+
const obsoleteFilePath = join(extensionsDirPath, '.obsolete')
82+
83+
if (extensionsDirPath && obsoleteFilePath) {
84+
const watchPattern = new vscode.RelativePattern(extensionsDirPath, '.obsolete')
85+
const fileWatcher = vscode.workspace.createFileSystemWatcher(watchPattern)
86+
87+
const checkUninstallHandler = () =>
88+
checkExtensionUninstall(extensionName, extensionsDirPath, obsoleteFilePath, callback)
89+
fileWatcher.onDidCreate(checkUninstallHandler)
90+
fileWatcher.onDidChange(checkUninstallHandler)
91+
92+
context.subscriptions.push(fileWatcher)
93+
}
94+
} catch (error) {
95+
getLogger().error(`UninstallExtension: Failed to register un-installation: ${error}`)
96+
}
97+
}

packages/core/src/shared/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,4 @@ export * as messages from './utilities/messages'
4949
export * as errors from './errors'
5050
export * as funcUtil from './utilities/functionUtils'
5151
export { fs } from './fs/fs'
52+
export * from './handleUninstall'
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Feature",
3+
"description": "Record telemetry event when AWS Toolkits extension is uninstalled."
4+
}

0 commit comments

Comments
 (0)