packages/core/src/extensionNode.ts (282 lines of code) (raw):
/*!
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
import * as vscode from 'vscode'
import * as nls from 'vscode-nls'
import * as codecatalyst from './codecatalyst/activation'
import { activate as activateAppBuilder } from './awsService/appBuilder/activation'
import { activate as activateAwsExplorer } from './awsexplorer/activation'
import { activate as activateCloudWatchLogs } from './awsService/cloudWatchLogs/activation'
import { activate as activateSchemas } from './eventSchemas/activation'
import { activate as activateLambda } from './lambda/activation'
import { activate as activateCloudFormationTemplateRegistry } from './shared/cloudformation/activation'
import { AwsContextCommands } from './shared/awsContextCommands'
import {
getIdeProperties,
getExtEnvironmentDetails,
isSageMaker,
showWelcomeMessage,
} from './shared/extensionUtilities'
import { getLogger, Logger } from './shared/logger/logger'
import { activate as activateEcr } from './awsService/ecr/activation'
import { activate as activateEc2, deactivate as deactivateEc2 } from './awsService/ec2/activation'
import { activate as activateSam } from './shared/sam/activation'
import { activate as activateS3 } from './awsService/s3/activation'
import * as filetypes from './shared/filetypes'
import { activate as activateApiGateway } from './awsService/apigateway/activation'
import { activate as activateStepFunctions } from './stepFunctions/activation'
import { activate as activateStepFunctionsWorkflowStudio } from './stepFunctions/workflowStudio/activation'
import { activate as activateSsmDocument } from './ssmDocument/activation'
import { activate as activateDynamicResources } from './dynamicResources/activation'
import { activate as activateEcs } from './awsService/ecs/activation'
import { activate as activateAppRunner } from './awsService/apprunner/activation'
import { activate as activateIot } from './awsService/iot/activation'
import { activate as activateDev } from './dev/activation'
import * as beta from './dev/beta'
import { activate as activateApplicationComposer } from './applicationcomposer/activation'
import { activate as activateRedshift } from './awsService/redshift/activation'
import { activate as activateDocumentDb } from './docdb/activation'
import { activate as activateIamPolicyChecks } from './awsService/accessanalyzer/activation'
import { activate as activateNotifications } from './notifications/activation'
import { SchemaService } from './shared/schemas'
import { AwsResourceManager } from './dynamicResources/awsResourceManager'
import globals from './shared/extensionGlobals'
import { Experiments, Settings, showSettingsFailedMsg } from './shared/settings'
import { isReleaseVersion } from './shared/vscode/env'
import { AuthStatus, AuthUserState, telemetry } from './shared/telemetry/telemetry'
import { ExtStartUpSources } from './shared/telemetry/util'
import { Auth } from './auth/auth'
import { getTelemetryMetadataForConn } from './auth/connection'
import { registerSubmitFeedback } from './feedback/vue/submitFeedback'
import { activateCommon, deactivateCommon } from './extension'
import { learnMoreAmazonQCommand, qExtensionPageCommand, dismissQTree } from './amazonq/explorer/amazonQChildrenNodes'
import { codeWhispererCoreScopes } from './codewhisperer/util/authUtil'
import { installAmazonQExtension } from './codewhisperer/commands/basicCommands'
import { VSCODE_EXTENSION_ID } from './shared/extensions'
import { isExtensionInstalled } from './shared/utilities/vsCodeUtils'
import { ExtensionUse, getAuthFormIdsFromConnection, initializeCredentialsProviderManager } from './auth/utils'
import { activate as activateThreatComposerEditor } from './threatComposer/activation'
import { isSsoConnection, hasScopes } from './auth/connection'
import { CrashMonitoring } from './shared/crashMonitoring'
import { setContext } from './shared/vscode/setContext'
import { AuthFormId } from './login/webview/vue/types'
let localize: nls.LocalizeFunc
/**
* The entrypoint for the nodejs version of the toolkit
*
* **CONTRIBUTORS** If you are adding code to this function prioritize adding it to
* {@link activateCommon} if appropriate
*/
export async function activate(context: vscode.ExtensionContext) {
const activationStartedOn = Date.now()
localize = nls.loadMessageBundle()
const contextPrefix = 'toolkit'
try {
// IMPORTANT: If you are doing setup that should also work in web mode (browser), it should be done in the function below
const extContext = await activateCommon(context, contextPrefix, false)
// Intentionally do not await since this can be slow and non-critical
void (await CrashMonitoring.instance())?.start()
initializeCredentialsProviderManager()
const toolkitEnvDetails = getExtEnvironmentDetails()
// Splits environment details by new line, filter removes the empty string
for (const line of toolkitEnvDetails.split(/\r?\n/).filter(Boolean)) {
getLogger().info(line)
}
globals.awsContextCommands = new AwsContextCommands(globals.regionProvider, Auth.instance)
globals.schemaService = new SchemaService()
globals.resourceManager = new AwsResourceManager(context)
const settings = Settings.instance
const experiments = Experiments.instance
experiments.onDidChange(({ key }) => {
telemetry.aws_experimentActivation.run((span) => {
// Record the key prior to reading the setting as `get` may throw
span.record({ experimentId: key })
span.record({ experimentState: experiments.get(key) ? 'activated' : 'deactivated' })
})
})
await globals.schemaService.start()
filetypes.activate()
try {
await activateDev(context)
await beta.activate(context)
} catch (error) {
getLogger().debug(`Developer Tools (internal): failed to activate: ${(error as Error).message}`)
}
context.subscriptions.push(registerSubmitFeedback(context, 'AWS Toolkit', contextPrefix))
// do not enable codecatalyst for sagemaker
// TODO: remove setContext if SageMaker adds the context to their IDE
if (!isSageMaker()) {
await setContext('aws.isSageMaker', false)
await codecatalyst.activate(extContext)
} else {
await setContext('aws.isSageMaker', true)
}
// wrap auth related setup in a context for telemetry
await telemetry.function_call.run(
async () => {
// Clean up remaining logins after codecatalyst activated and ran its cleanup.
// Because we are splitting auth sessions by extension, we can't use Amazon Q
// connections anymore.
// TODO: Remove after some time?
for (const conn of await Auth.instance.listConnections()) {
if (isSsoConnection(conn) && hasScopes(conn, codeWhispererCoreScopes)) {
getLogger().debug(
`forgetting connection: ${conn.id} with starturl/scopes: ${conn.startUrl} / %O`,
conn.scopes
)
await Auth.instance.forgetConnection(conn)
}
}
},
{ emit: false, functionId: { name: 'activate', class: 'ExtensionNodeCore' } }
)
await activateCloudFormationTemplateRegistry(context)
await activateAwsExplorer({
context: extContext,
regionProvider: globals.regionProvider,
toolkitOutputChannel: globals.outputChannel,
})
await activateAppRunner(extContext)
await activateApiGateway({
extContext: extContext,
outputChannel: globals.outputChannel,
})
await activateLambda(extContext)
await activateSsmDocument(context, globals.awsContext, globals.regionProvider, globals.outputChannel)
await activateSam(extContext)
await activateS3(extContext)
await activateEc2(extContext)
await activateEcr(context)
await activateCloudWatchLogs(context, settings)
await activateDynamicResources(context)
await activateIot(extContext)
await activateEcs(extContext)
await activateSchemas(extContext)
if (!isSageMaker()) {
// Amazon Q Tree setup.
learnMoreAmazonQCommand.register()
qExtensionPageCommand.register()
dismissQTree.register()
installAmazonQExtension.register()
await handleAmazonQInstall()
}
await activateApplicationComposer(context)
await activateThreatComposerEditor(context)
await activateStepFunctions(context, globals.awsContext, globals.outputChannel)
await activateStepFunctionsWorkflowStudio()
await activateRedshift(extContext)
await activateAppBuilder(extContext)
await activateDocumentDb(extContext)
await activateIamPolicyChecks(extContext)
context.subscriptions.push(
vscode.window.registerUriHandler({
handleUri: (uri) =>
telemetry.runRoot(() => {
telemetry.record({ source: 'UriHandler' })
return globals.uriHandler.handleUri(uri)
}),
})
)
showWelcomeMessage(context)
const settingsValid = await settings.isReadable()
if (!settingsValid) {
void showSettingsFailedMsg('read')
}
recordToolkitInitialization(activationStartedOn, settingsValid, getLogger())
if (!isReleaseVersion()) {
globals.telemetry.assertPassiveTelemetry(globals.didReload)
}
// TODO: Should probably emit for web as well.
// Will the web metric look the same?
telemetry.auth_userState.emit({
passive: true,
result: 'Succeeded',
source: ExtensionUse.instance.sourceForTelemetry(),
...(await getAuthState()),
})
void activateNotifications(context, getAuthState)
} catch (error) {
const stacktrace = (error as Error).stack?.split('\n')
// truncate if the stacktrace is unusually long
if (stacktrace !== undefined && stacktrace.length > 40) {
stacktrace.length = 40
}
getLogger().error(
localize(
'AWS.channel.aws.toolkit.activation.error',
'Error Activating {0} Toolkit: {1} \n{2}',
getIdeProperties().company,
(error as Error).message,
stacktrace?.join('\n')
)
)
throw error
}
}
export async function deactivate() {
// Run concurrently to speed up execution. stop() does not throw so it is safe
await Promise.all([await (await CrashMonitoring.instance())?.shutdown(), deactivateCommon(), deactivateEc2()])
globals.sdkClientBuilderV3.clearServiceCache()
await globals.resourceManager.dispose()
}
async function handleAmazonQInstall() {
const dismissedInstall = globals.globalState.get<boolean>('aws.toolkit.amazonqInstall.dismissed')
if (dismissedInstall) {
return
}
if (isExtensionInstalled(VSCODE_EXTENSION_ID.amazonq)) {
await globals.globalState.update('aws.toolkit.amazonqInstall.dismissed', true)
return
}
await telemetry.toolkit_showNotification.run(async () => {
telemetry.record({ id: 'amazonQStandaloneChange' })
void vscode.window
.showInformationMessage(
'Try Amazon Q, a generative AI assistant, with chat and code suggestions.',
'Install',
'Learn More'
)
.then(async (resp) => {
await telemetry.toolkit_invokeAction.run(async () => {
telemetry.record({
source: ExtensionUse.instance.isFirstUse()
? ExtStartUpSources.firstStartUp
: ExtStartUpSources.none,
})
if (resp === 'Learn More') {
// Clicking learn more will open the q extension page
telemetry.record({ action: 'learnMore' })
await qExtensionPageCommand.execute()
return
}
if (resp === 'Install') {
telemetry.record({ action: 'installAmazonQ' })
await installAmazonQExtension.execute()
} else {
telemetry.record({ action: 'dismissQNotification' })
}
await globals.globalState.update('aws.toolkit.amazonqInstall.dismissed', true)
})
})
})
}
function recordToolkitInitialization(activationStartedOn: number, settingsValid: boolean, logger?: Logger) {
try {
const activationFinishedOn = Date.now()
const duration = activationFinishedOn - activationStartedOn
if (settingsValid) {
telemetry.toolkit_init.emit({ duration, result: 'Succeeded' })
} else {
telemetry.toolkit_init.emit({ duration, result: 'Failed', reason: 'UserSettingsRead' })
}
} catch (err) {
logger?.error(err as Error)
}
}
async function getAuthState(): Promise<Omit<AuthUserState, 'source'>> {
let authStatus: AuthStatus = 'notConnected'
const enabledConnections: Set<AuthFormId> = new Set()
const enabledScopes: Set<string> = new Set()
if (Auth.instance.hasConnections) {
authStatus = 'expired'
for (const conn of await Auth.instance.listConnections()) {
const state = Auth.instance.getConnectionState(conn)
if (state === 'valid') {
authStatus = 'connected'
}
for (const id of getAuthFormIdsFromConnection(conn)) {
enabledConnections.add(id)
}
if (isSsoConnection(conn)) {
if (conn.scopes) {
for (const s of conn.scopes) {
enabledScopes.add(s)
}
}
}
}
}
// There may be other SSO connections in toolkit, but there is no use case for
// displaying registration info for non-active connections at this time.
const activeConn = Auth.instance.activeConnection
if (activeConn?.type === 'sso') {
telemetry.record(await getTelemetryMetadataForConn(activeConn))
}
return {
authStatus,
authEnabledConnections: [...enabledConnections].sort().join(','),
authScopes: [...enabledScopes].sort().join(','),
}
}