Skip to content

Commit 14f14d2

Browse files
refactor: node/web/common file structure (#5336)
* doc: update shared, common, web, node subfolder structure Signed-off-by: Nikolas Komonen <[email protected]> * refactor: move `crypto` module to `shared/` We are moving everything out of the `common/` folder since we do not need this anymore. Signed-off-by: Nikolas Komonen <[email protected]> * refactor: move `request` in to `shared/` This removes all modules from the `common/` folder. Signed-off-by: Nikolas Komonen <[email protected]> * doc: update web docs Signed-off-by: Nikolas Komonen <[email protected]> * refactor: export a Node only codewhisperer module Problem: We have some code, specifically Transform/Gumby, that only works in Node.js. Then in `codewhisperer/index.ts` we export many CW related code including this gumby code. The issue arises when we import this `index.ts` in to Web only code as it transitively imports the gumby code and this breaks due to them using fs-extra. Solution: Create a `codewhisperer/indexNode.ts` file and a `codewhisperer/index.ts` file. - `indexNode` is a superset of `index`. - `indexNode` will additionally include Node only code - `index` will include "common" code, meaning it works in Web or Node - Note this is not 100% true yet as there may still be node only code in this. We will just need to move it to `indexNode` over time Signed-off-by: Nikolas Komonen <[email protected]> * refactor: remove redundant exports There were some redundant exports that used "as". This removes them and updates to use the direct import instead. Signed-off-by: Nikolas Komonen <[email protected]> * refactor: extension.ts -> extensionNode.ts Adds the "node" keyword to the extension file and updates exports/imports. We want "extension.ts" to represent common code going forward. Signed-off-by: Nikolas Komonen <[email protected]> * refactor: extensionCommon.ts -> extension.ts Now the extension.ts files will implicitly be "common" files and extensionNode will contain Node specific code Signed-off-by: Nikolas Komonen <[email protected]> * minor rename Signed-off-by: Nikolas Komonen <[email protected]> * minor doc fix Signed-off-by: Nikolas Komonen <[email protected]> --------- Signed-off-by: Nikolas Komonen <[email protected]>
1 parent 5397c1b commit 14f14d2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+914
-950
lines changed

docs/arch_develop.md

Lines changed: 29 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -71,39 +71,35 @@ Some components of the core library depend on the `package.json`s of the extensi
7171
- Does not restore, it is a superset of what exists in `packages/core` for `configuration.properties`.
7272
- To develop for the Amazon Q extension: add all changes to `packages/amazonq/package.json`, EXCEPT for settings that are references by code in the core library, or settings that already exist in the core `package.json`
7373

74-
## Shared vs Common names
74+
## `web`, `node`, `common`, `shared` naming conventions
7575

76-
In this repo, the keywords **"shared"** and **"common"** have specific meanings in the context of file/folder names.
76+
This project can run in different environments, eg Web mode (in the browser with no compute backend), or in Node.js on your desktop (the most common way).
77+
A problem arises when we use code that is exclusive to one environment, an example being Node.js' Filesystem module which will fail in Web mode.
7778

78-
### "common"
79+
To ensure developers use compatible code for their environment we have subfolders in each topic which contains environment specific code in a single place.
7980

80-
Code within a folder/file that has the "common" keyword implies that it can run in any environment. Examples of environments are: Web mode, or Node.js (local desktop)
81+
Using this file tree as reference, here are the rules:
8182

82-
We need this distinction since not all code can run in any environment. A common example is filesystem code, where the actual implementation used could work in Node.js but not in Web mode.
83-
84-
### "shared"
85-
86-
Code within a folder/file that has the "shared" keyword implies that it is intended to be reused wherever it can be. This is generalized code that "Feature A" or "Feature B" could use if it works for their use case.
87-
88-
An example is the `waitUntil()` function which continuously invokes an arbitrary callback function until it succeeds.
89-
90-
> NOTE: Something that is "shared" does not mean it is "common", as it could be reused in different places but only work in Node.js for example.
83+
```
84+
src/
85+
├── myTopic/
86+
│ ├── {file}.ts
87+
│ ├── node/
88+
│ │ └── {file}.ts
89+
│ └── web/
90+
│ └── {file}.ts
91+
└── shared/
92+
```
9193

92-
### How to apply this
94+
- `myTopic/` is the general name of the folder, eg `request` for http requests.
95+
- `myTopic/{file}.ts` is for code that works in any environment, we refer to this as `"common"` code.
96+
- `node/{file}.ts` is for code that works exclusively in Node.js.
97+
- `web/{file}.ts` is for code that works exclusively in Web mode.
98+
- `shared/` is for code that is intended to be reused, i.e general purpose utils.
99+
- Note environment specific code should be place in to a `web/` or `node/` subfolder.
100+
- If the code is not in a subfolder then it is considered `shared common` code.
93101

94-
- Aim to make code compatible with "common" from the beginning.
95-
- In a "topic" folder, if you have common code, create a subfolder named "common" and add your common code to there.
96-
```
97-
src/
98-
|
99-
myTopic/
100-
|
101-
common/
102-
nonCommon.ts
103-
```
104-
- See if yours, or existing code can be moved in to a "shared" folder. Maybe it can be easily modified to become "shared".
105-
- If there is no "shared" or "common" naming used for the file/folder, then assume it only works in Node.js.
106-
- In the rare case your code only works in Web mode, create a `web` subfolder for that code.
102+
> IMPORTANT: The current codebase does not fully follow this convention yet, the transition is being done incrementally. Due to this, code that is `"common"` may not actually be common yet. If you run in to this, please move that code to the appropriate subfolder.
107103
108104
## Commands
109105

@@ -292,22 +288,22 @@ Commands and events are defined on the backend via sub-classes of `VueWebview`.
292288
```ts
293289
client
294290
.foo()
295-
.then(response => console.log(response))
296-
.catch(err => console.log(err))
291+
.then((response) => console.log(response))
292+
.catch((err) => console.log(err))
297293
```
298294

299295
The backend protocol is allowed to throw errors. These result in rejected Promises on the frontend.
300296

301297
- Registering for events:
302298

303299
```ts
304-
client.onBar(num => console.log(num))
300+
client.onBar((num) => console.log(num))
305301
```
306302

307303
- Methods called `init` will only return data on the initial webview load:
308304

309305
```ts
310-
client.init(data => (this.data = data ?? this.data))
306+
client.init((data) => (this.data = data ?? this.data))
311307
```
312308

313309
## Webviews (non Vue)
@@ -493,8 +489,8 @@ class ExampleWizard extends Wizard<ExampleState> {
493489
{ label: '1', data: 1 },
494490
{ label: '2', data: 2 },
495491
]
496-
this.form.bar.bindPrompter(state => createQuickPick(items, { title: `Select a number (${state.foo})` }), {
497-
showWhen: state => state.foo?.length > 5,
492+
this.form.bar.bindPrompter((state) => createQuickPick(items, { title: `Select a number (${state.foo})` }), {
493+
showWhen: (state) => state.foo?.length > 5,
498494
})
499495
}
500496
}

docs/web.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ VS Code window, in the background it is running in a Browser context.
8585

8686
## Adding Web mode specific npm modules
8787

88-
If you need to manage npm modules required for Web mode, such as a [browserfied module](https://www.npmjs.com/package/os-browserify), see [the documentation here](../packages/core/src/web/README.md).
88+
If you need to manage npm modules required for Web mode, such as a [browserfied module](https://www.npmjs.com/package/os-browserify), see [the documentation here](../packages/core/src/web/README.md#packagejson).
8989

9090
## Finding incompatible transitive dependencies
9191

packages/amazonq/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
"onCommand:aws.amazonq.accept",
4343
"onView:aws.codeWhisperer.securityPanel"
4444
],
45-
"main": "./dist/src/extension",
45+
"main": "./dist/src/extensionNode",
4646
"browser": "./dist/src/extensionWeb",
4747
"scripts": {
4848
"vscode:prepublish": "npm run clean && npm run buildScripts && webpack --mode production",

packages/amazonq/src/extension.ts

Lines changed: 138 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -3,90 +3,155 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6+
import { AuthUtils, CredentialsStore, LoginManager, SsoConnection, initializeAuth } from 'aws-core-vscode/auth'
7+
import {
8+
AuthUtil,
9+
activate as activateCodeWhisperer,
10+
shutdown as shutdownCodeWhisperer,
11+
} from 'aws-core-vscode/codewhisperer'
12+
import { makeEndpointsProvider, registerGenericCommands } from 'aws-core-vscode'
13+
import { CommonAuthWebview } from 'aws-core-vscode/login'
14+
import {
15+
DefaultAWSClientBuilder,
16+
DefaultAwsContext,
17+
ExtContext,
18+
RegionProvider,
19+
Settings,
20+
activateLogger,
21+
activateTelemetry,
22+
env,
23+
errors,
24+
fs,
25+
getLogger,
26+
getMachineId,
27+
globals,
28+
initialize,
29+
initializeComputeRegion,
30+
messages,
31+
setContext,
32+
} from 'aws-core-vscode/shared'
33+
import { ExtStartUpSources, telemetry } from 'aws-core-vscode/telemetry'
34+
import { VSCODE_EXTENSION_ID } from 'aws-core-vscode/utils'
35+
import { join } from 'path'
36+
import * as semver from 'semver'
637
import * as vscode from 'vscode'
7-
import { activateAmazonQCommon, amazonQContextPrefix, deactivateCommon } from './extensionCommon'
8-
import { DefaultAmazonQAppInitContext } from 'aws-core-vscode/amazonq'
9-
import { activate as activateQGumby } from 'aws-core-vscode/amazonqGumby'
10-
import { ExtContext } from 'aws-core-vscode/shared'
11-
import { updateDevMode } from 'aws-core-vscode/dev'
12-
import { CommonAuthViewProvider } from 'aws-core-vscode/login'
13-
import { isExtensionActive, VSCODE_EXTENSION_ID } from 'aws-core-vscode/utils'
14-
import { registerSubmitFeedback } from 'aws-core-vscode/feedback'
15-
import { DevOptions } from 'aws-core-vscode/dev'
16-
import { Auth } from 'aws-core-vscode/auth'
17-
import api from './api'
18-
import { activate as activateCWChat } from './app/chat/activation'
19-
20-
export async function activate(context: vscode.ExtensionContext) {
21-
// IMPORTANT: No other code should be added to this function. Place it in one of the following 2 functions where appropriate.
22-
await activateAmazonQCommon(context, false)
23-
await activateAmazonQNonCommon(context)
24-
25-
return api
26-
}
38+
import { registerCommands } from './commands'
39+
40+
export const amazonQContextPrefix = 'amazonq'
2741

2842
/**
29-
* The code in this function is not common, implying it only works in Node.js and not web.
30-
* The goal should be for this to not exist and that all code is "common". So if possible make
31-
* the code compatible with web and move it to {@link activateAmazonQCommon}.
43+
* Activation code for Amazon Q that will we want in all environments (eg Node.js, web mode)
3244
*/
33-
async function activateAmazonQNonCommon(context: vscode.ExtensionContext) {
45+
export async function activateAmazonQCommon(context: vscode.ExtensionContext, isWeb: boolean) {
46+
initialize(context, isWeb)
47+
const homeDirLogs = await fs.init(context, (homeDir) => {
48+
void messages.showViewLogsMessage(`Invalid home directory (check $HOME): "${homeDir}"`)
49+
})
50+
errors.init(fs.getUsername(), env.isAutomation())
51+
await initializeComputeRegion()
52+
53+
globals.contextPrefix = 'amazonq.' //todo: disconnect from above line
54+
55+
// Avoid activation if older toolkit is installed
56+
// Amazon Q is only compatible with AWS Toolkit >= 3.0.0
57+
// Or AWS Toolkit with a development version. Example: 2.19.0-3413gv
58+
const toolkit = vscode.extensions.getExtension(VSCODE_EXTENSION_ID.awstoolkit)
59+
if (toolkit) {
60+
const toolkitVersion = semver.coerce(toolkit.packageJSON.version)
61+
// XXX: can't use `SemVer.prerelease` because Toolkit "prerelease" (git sha) is not a valid
62+
// semver prerelease: it may start with a number.
63+
const isDevVersion = toolkit.packageJSON.version.toString().includes('-')
64+
if (toolkitVersion && toolkitVersion.major < 3 && !isDevVersion) {
65+
await vscode.commands
66+
.executeCommand('workbench.extensions.installExtension', VSCODE_EXTENSION_ID.awstoolkit)
67+
.then(
68+
() =>
69+
vscode.window
70+
.showInformationMessage(
71+
`The Amazon Q extension is incompatible with AWS Toolkit ${
72+
toolkitVersion as any
73+
} and older. Your AWS Toolkit was updated to version 3.0 or later.`,
74+
'Reload Now'
75+
)
76+
.then(async (resp) => {
77+
if (resp === 'Reload Now') {
78+
await vscode.commands.executeCommand('workbench.action.reloadWindow')
79+
}
80+
}),
81+
(reason) => {
82+
getLogger().error('workbench.extensions.installExtension failed: %O', reason)
83+
}
84+
)
85+
return
86+
}
87+
}
88+
89+
globals.machineId = await getMachineId()
90+
globals.awsContext = new DefaultAwsContext()
91+
globals.sdkClientBuilder = new DefaultAWSClientBuilder(globals.awsContext)
92+
globals.manifestPaths.endpoints = context.asAbsolutePath(join('resources', 'endpoints.json'))
93+
globals.regionProvider = RegionProvider.fromEndpointsProvider(makeEndpointsProvider())
94+
95+
const qOutputChannel = vscode.window.createOutputChannel('Amazon Q', { log: true })
96+
const qLogChannel = vscode.window.createOutputChannel('Amazon Q Logs', { log: true })
97+
await activateLogger(context, amazonQContextPrefix, qOutputChannel, qLogChannel)
98+
globals.outputChannel = qOutputChannel
99+
globals.logOutputChannel = qLogChannel
100+
globals.loginManager = new LoginManager(globals.awsContext, new CredentialsStore())
101+
102+
if (homeDirLogs.length > 0) {
103+
getLogger().error('fs.init: invalid env vars found: %O', homeDirLogs)
104+
}
105+
106+
await activateTelemetry(context, globals.awsContext, Settings.instance, 'Amazon Q For VS Code')
107+
108+
await initializeAuth(globals.loginManager)
109+
34110
const extContext = {
35111
extensionContext: context,
36112
}
37-
await activateCWChat(context)
38-
await activateQGumby(extContext as ExtContext)
39-
40-
const authProvider = new CommonAuthViewProvider(
41-
context,
42-
amazonQContextPrefix,
43-
DefaultAmazonQAppInitContext.instance.onDidChangeAmazonQVisibility
44-
)
45-
context.subscriptions.push(
46-
vscode.window.registerWebviewViewProvider(authProvider.viewType, authProvider, {
47-
webviewOptions: {
48-
retainContextWhenHidden: true,
49-
},
50-
}),
51-
registerSubmitFeedback(context, 'Amazon Q', amazonQContextPrefix)
52-
)
53-
54-
await setupDevMode(context)
55-
}
113+
await activateCodeWhisperer(extContext as ExtContext)
56114

57-
/**
58-
* Some parts of this do not work in Web mode so we need to set Dev Mode up here.
59-
*
60-
* TODO: Get the following working in web mode as well and then move this function.
61-
*/
62-
async function setupDevMode(context: vscode.ExtensionContext) {
63-
// At some point this imports CodeCatalyst code which breaks in web mode.
64-
// TODO: Make this work in web mode and move it to extensionCommon.ts
65-
await updateDevMode()
66-
67-
const devOptions: DevOptions = {
68-
context,
69-
auth: Auth.instance,
70-
menuOptions: [
71-
'editStorage',
72-
'showEnvVars',
73-
'deleteSsoConnections',
74-
'expireSsoConnections',
75-
'editAuthConnections',
76-
],
115+
// Generic extension commands
116+
registerGenericCommands(context, amazonQContextPrefix)
117+
118+
// Amazon Q specific commands
119+
registerCommands(context)
120+
121+
// Hide the Amazon Q tree in toolkit explorer
122+
await setContext('aws.toolkit.amazonq.dismissed', true)
123+
124+
// reload webviews
125+
await vscode.commands.executeCommand('workbench.action.webview.reloadWebviewAction')
126+
127+
if (AuthUtils.ExtensionUse.instance.isFirstUse()) {
128+
CommonAuthWebview.authSource = ExtStartUpSources.firstStartUp
129+
await vscode.commands.executeCommand('workbench.view.extension.amazonq')
77130
}
78131

79-
context.subscriptions.push(
80-
vscode.commands.registerCommand('amazonq.dev.openMenu', async () => {
81-
if (!isExtensionActive(VSCODE_EXTENSION_ID.awstoolkit)) {
82-
void vscode.window.showErrorMessage('AWS Toolkit must be installed to access the Developer Menu.')
83-
return
84-
}
85-
await vscode.commands.executeCommand('_aws.dev.invokeMenu', devOptions)
132+
await telemetry.auth_userState.run(async () => {
133+
telemetry.record({ passive: true })
134+
135+
const firstUse = AuthUtils.ExtensionUse.instance.isFirstUse()
136+
const wasUpdated = AuthUtils.ExtensionUse.instance.wasUpdated()
137+
138+
if (firstUse) {
139+
telemetry.record({ source: ExtStartUpSources.firstStartUp })
140+
} else if (wasUpdated) {
141+
telemetry.record({ source: ExtStartUpSources.update })
142+
} else {
143+
telemetry.record({ source: ExtStartUpSources.reload })
144+
}
145+
146+
const authState = (await AuthUtil.instance.getChatAuthState()).codewhispererChat
147+
telemetry.record({
148+
authStatus: authState === 'connected' || authState === 'expired' ? authState : 'notConnected',
149+
authEnabledConnections: AuthUtils.getAuthFormIdsFromConnection(AuthUtil.instance.conn).join(','),
150+
authScopes: ((AuthUtil.instance.conn as SsoConnection)?.scopes ?? []).join(','),
86151
})
87-
)
152+
})
88153
}
89154

90-
export async function deactivate() {
91-
await deactivateCommon()
155+
export async function deactivateCommon() {
156+
await shutdownCodeWhisperer()
92157
}

0 commit comments

Comments
 (0)