Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,19 @@ Example:
}
```

<a name="amazonqLsp-settings">Overrides specifically for the Amazon Q language server can be set using the `aws.dev.amazonqLsp` setting. This is a JSON object consisting of keys/values required to override language server: `manifestUrl`, `supportedVersions`, `id`, and `locationOverride`.</a>

Example:

```json
"aws.dev.amazonqLsp": {
"manifestUrl": "https://custom.url/manifest.json",
"supportedVersions": "4.0.0",
"id": "AmazonQ",
"locationOverride": "/custom/path/to/local/lsp/folder",
}
```

### Environment variables

Environment variables can be used to modify the behaviour of VSCode. The following are environment variables that can be used to configure the extension:
Expand Down Expand Up @@ -472,6 +485,10 @@ Unlike the user setting overrides, not all of these environment variables have t

- `__CODEWHISPERER_REGION`: for aws.dev.codewhispererService.region
- `__CODEWHISPERER_ENDPOINT`: for aws.dev.codewhispererService.endpoint
- `__AMAZONQLSP_MANIFEST_URL`: for aws.dev.amazonqLsp.manifestUrl
- `__AMAZONQLSP_SUPPORTED_VERSIONS`: for aws.dev.amazonqLsp.supportedVersions
- `__AMAZONQLSP_ID`: for aws.dev.amazonqLsp.id
- `__AMAZONQLSP_LOCATION_OVERRIDE`: for aws.dev.amazonqLsp.locationOverride

#### Lambda

Expand Down
2 changes: 1 addition & 1 deletion packages/amazonq/.vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"env": {
"SSMDOCUMENT_LANGUAGESERVER_PORT": "6010",
"WEBPACK_DEVELOPER_SERVER": "http://localhost:8080"
// "AWS_LANGUAGE_SERVER_OVERRIDE": "${workspaceFolder}/../../../language-servers/app/aws-lsp-codewhisperer-runtimes/out/token-standalone.js",
// "__AMAZONQLSP_LOCATION_OVERRIDE": "${workspaceFolder}/../../../language-servers/app/aws-lsp-codewhisperer-runtimes/out/token-standalone.js",
},
"envFile": "${workspaceFolder}/.local.env",
"outFiles": ["${workspaceFolder}/dist/**/*.js", "${workspaceFolder}/../core/dist/**/*.js"],
Expand Down
28 changes: 28 additions & 0 deletions packages/amazonq/src/lsp/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*!
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

import { DevSettings, getServiceEnvVarConfig } from 'aws-core-vscode/shared'

export interface AmazonQLspConfig {
manifestUrl: string
supportedVersions: string
id: string
locationOverride?: string
}

export const defaultAmazonQLspConfig: AmazonQLspConfig = {
manifestUrl: 'https://aws-toolkit-language-servers.amazonaws.com/codewhisperer/0/manifest.json',
supportedVersions: '^3.1.1',
id: 'AmazonQ', // used for identification in global storage/local disk location. Do not change.
locationOverride: undefined,
}

export function getAmazonQLspConfig(): AmazonQLspConfig {
return {
...defaultAmazonQLspConfig,
...(DevSettings.instance.getServiceConfig('amazonqLsp', {}) as AmazonQLspConfig),
...getServiceEnvVarConfig('amazonqLsp', Object.keys(defaultAmazonQLspConfig)),
}
}
25 changes: 11 additions & 14 deletions packages/amazonq/src/lsp/lspInstaller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,37 +16,34 @@ import {
} from 'aws-core-vscode/shared'
import path from 'path'
import { Range } from 'semver'
import { getAmazonQLspConfig } from './config'

export const manifestURL = 'https://aws-toolkit-language-servers.amazonaws.com/codewhisperer/0/manifest.json'
export const supportedLspServerVersions = new Range('^3.1.1', {
includePrerelease: true,
})
const logger = getLogger('amazonqLsp')

export class AmazonQLSPResolver implements LspResolver {
async resolve(): Promise<LspResolution> {
const overrideLocation = process.env.AWS_LANGUAGE_SERVER_OVERRIDE
if (overrideLocation) {
logger.info(`Using language server override location: ${overrideLocation}`)
void vscode.window.showInformationMessage(`Using language server override location: ${overrideLocation}`)
const { id, manifestUrl, supportedVersions, locationOverride } = getAmazonQLspConfig()
if (locationOverride) {
void vscode.window.showInformationMessage(`Using language server override location: ${locationOverride}`)
return {
assetDirectory: overrideLocation,
assetDirectory: locationOverride,
location: 'override',
version: '0.0.0',
resourcePaths: {
lsp: overrideLocation,
lsp: locationOverride,
node: getNodeExecutableName(),
},
}
}

// "AmazonQ" is shared across toolkits to provide a common access point, don't change it
const name = 'AmazonQ'
const manifest = await new ManifestResolver(manifestURL, name).resolve()
const manifest = await new ManifestResolver(manifestUrl, id).resolve()
const installationResult = await new LanguageServerResolver(
manifest,
name,
supportedLspServerVersions
id,
new Range(supportedVersions, {
includePrerelease: true,
})
).resolve()

const nodePath = path.join(installationResult.assetDirectory, `servers/${getNodeExecutableName()}`)
Expand Down
56 changes: 38 additions & 18 deletions packages/amazonq/test/e2e/lsp/lspInstaller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@

import assert from 'assert'
import sinon from 'sinon'
import { AmazonQLSPResolver, manifestURL, supportedLspServerVersions } from '../../../src/lsp/lspInstaller'
import { AmazonQLSPResolver } from '../../../src/lsp/lspInstaller'
import {
DevSettings,
fs,
globals,
LanguageServerResolver,
Expand All @@ -19,6 +20,7 @@ import * as semver from 'semver'
import { assertTelemetry } from 'aws-core-vscode/test'
import { LspController } from 'aws-core-vscode/amazonq'
import { LanguageServerSetup } from 'aws-core-vscode/telemetry'
import { AmazonQLspConfig, getAmazonQLspConfig } from '../../../src/lsp/config'

function createVersion(version: string) {
return {
Expand Down Expand Up @@ -48,9 +50,11 @@ describe('AmazonQLSPInstaller', () => {
// If globalState contains an ETag that is up to date with remote, we won't fetch it resulting in inconsistent behavior.
// Therefore, we clear it temporarily for these tests to ensure consistent behavior.
let manifestStorage: { [key: string]: any }
let lspConfig: AmazonQLspConfig

before(async () => {
manifestStorage = globals.globalState.get(manifestStorageKey) || {}
lspConfig = getAmazonQLspConfig()
})

beforeEach(async () => {
Expand All @@ -64,7 +68,7 @@ describe('AmazonQLSPInstaller', () => {
})

afterEach(async () => {
delete process.env.AWS_LANGUAGE_SERVER_OVERRIDE
delete process.env.__AMAZONQLSP_LOCATION_OVERRIDE
sandbox.restore()
await fs.delete(tempDir, {
recursive: true,
Expand All @@ -76,9 +80,25 @@ describe('AmazonQLSPInstaller', () => {
})

describe('resolve()', () => {
it('uses AWS_LANGUAGE_SERVER_OVERRIDE', async () => {
it('uses dev setting override', async () => {
const locationOverride = '/custom/path/to/lsp'
const serviceConfigStub = sandbox.stub().returns({
locationOverride,
})
sandbox.stub(DevSettings, 'instance').get(() => ({
getServiceConfig: serviceConfigStub,
}))

const result = await resolver.resolve()

assert.strictEqual(result.assetDirectory, locationOverride)
assert.strictEqual(result.location, 'override')
assert.strictEqual(result.version, '0.0.0')
})

it('uses environment variable override', async () => {
const overridePath = '/custom/path/to/lsp'
process.env.AWS_LANGUAGE_SERVER_OVERRIDE = overridePath
process.env.__AMAZONQLSP_LOCATION_OVERRIDE = overridePath

const result = await resolver.resolve()

Expand All @@ -93,14 +113,14 @@ describe('AmazonQLSPInstaller', () => {

assert.ok(download.assetDirectory.startsWith(tempDir))
assert.deepStrictEqual(download.location, 'remote')
assert.ok(semver.satisfies(download.version, supportedLspServerVersions))
assert.ok(semver.satisfies(download.version, lspConfig.supportedVersions))

// Second try - Should see the contents in the cache
const cache = await resolver.resolve()

assert.ok(cache.assetDirectory.startsWith(tempDir))
assert.deepStrictEqual(cache.location, 'cache')
assert.ok(semver.satisfies(cache.version, supportedLspServerVersions))
assert.ok(semver.satisfies(cache.version, lspConfig.supportedVersions))

/**
* Always make sure the latest version is one patch higher. This stops a problem
Expand Down Expand Up @@ -135,7 +155,7 @@ describe('AmazonQLSPInstaller', () => {

assert.ok(fallback.assetDirectory.startsWith(tempDir))
assert.deepStrictEqual(fallback.location, 'fallback')
assert.ok(semver.satisfies(fallback.version, supportedLspServerVersions))
assert.ok(semver.satisfies(fallback.version, lspConfig.supportedVersions))

/* First Try Telemetry
getManifest: remote succeeds
Expand All @@ -144,25 +164,25 @@ describe('AmazonQLSPInstaller', () => {
*/
const firstTryTelemetry: Partial<LanguageServerSetup>[] = [
{
id: 'AmazonQ',
id: lspConfig.id,
manifestLocation: 'remote',
languageServerSetupStage: 'getManifest',
result: 'Succeeded',
},
{
id: 'AmazonQ',
id: lspConfig.id,
languageServerLocation: 'cache',
languageServerSetupStage: 'getServer',
result: 'Failed',
},
{
id: 'AmazonQ',
id: lspConfig.id,
languageServerLocation: 'remote',
languageServerSetupStage: 'validate',
result: 'Succeeded',
},
{
id: 'AmazonQ',
id: lspConfig.id,
languageServerLocation: 'remote',
languageServerSetupStage: 'getServer',
result: 'Succeeded',
Expand All @@ -176,19 +196,19 @@ describe('AmazonQLSPInstaller', () => {
*/
const secondTryTelemetry: Partial<LanguageServerSetup>[] = [
{
id: 'AmazonQ',
id: lspConfig.id,
manifestLocation: 'remote',
languageServerSetupStage: 'getManifest',
result: 'Failed',
},
{
id: 'AmazonQ',
id: lspConfig.id,
manifestLocation: 'cache',
languageServerSetupStage: 'getManifest',
result: 'Succeeded',
},
{
id: 'AmazonQ',
id: lspConfig.id,
languageServerLocation: 'cache',
languageServerSetupStage: 'getServer',
result: 'Succeeded',
Expand All @@ -202,19 +222,19 @@ describe('AmazonQLSPInstaller', () => {
*/
const thirdTryTelemetry: Partial<LanguageServerSetup>[] = [
{
id: 'AmazonQ',
id: lspConfig.id,
languageServerLocation: 'cache',
languageServerSetupStage: 'getServer',
result: 'Failed',
},
{
id: 'AmazonQ',
id: lspConfig.id,
languageServerLocation: 'remote',
languageServerSetupStage: 'getServer',
result: 'Failed',
},
{
id: 'AmazonQ',
id: lspConfig.id,
languageServerLocation: 'fallback',
languageServerSetupStage: 'getServer',
result: 'Succeeded',
Expand All @@ -227,7 +247,7 @@ describe('AmazonQLSPInstaller', () => {
})

it('resolves release candidiates', async () => {
const original = new ManifestResolver(manifestURL, 'AmazonQ').resolve()
const original = new ManifestResolver(lspConfig.manifestUrl, lspConfig.id).resolve()
sandbox.stub(ManifestResolver.prototype, 'resolve').callsFake(async () => {
const originalManifest = await original

Expand Down
79 changes: 79 additions & 0 deletions packages/amazonq/test/unit/amazonq/lsp/config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*!
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

import assert from 'assert'
import { DevSettings } from 'aws-core-vscode/shared'
import sinon from 'sinon'
import { defaultAmazonQLspConfig, getAmazonQLspConfig } from '../../../../src/lsp/config'

describe('getAmazonQLspConfig', () => {
let sandbox: sinon.SinonSandbox
let serviceConfigStub: sinon.SinonStub
const settingConfig = {
manifestUrl: 'https://custom.url/manifest.json',
supportedVersions: '4.0.0',
id: 'AmazonQSetting',
locationOverride: '/custom/path',
}

beforeEach(() => {
sandbox = sinon.createSandbox()

serviceConfigStub = sandbox.stub()
sandbox.stub(DevSettings, 'instance').get(() => ({
getServiceConfig: serviceConfigStub,
}))
})

afterEach(() => {
sandbox.restore()
})

it('uses default config', () => {
serviceConfigStub.returns({})
const config = getAmazonQLspConfig()
assert.deepStrictEqual(config, defaultAmazonQLspConfig)
})

it('overrides location', () => {
const locationOverride = '/custom/path/to/lsp'
serviceConfigStub.returns({ locationOverride })

const config = getAmazonQLspConfig()
assert.deepStrictEqual(config, {
...defaultAmazonQLspConfig,
locationOverride,
})
})

it('overrides default settings', () => {
serviceConfigStub.returns(settingConfig)

const config = getAmazonQLspConfig()
assert.deepStrictEqual(config, settingConfig)
})

it('environment variable takes precedence over settings', () => {
const envConfig = {
manifestUrl: 'https://another-custom.url/manifest.json',
supportedVersions: '5.1.1',
id: 'AmazonQEnv',
locationOverride: '/some/new/custom/path',
}

process.env.__AMAZONQLSP_MANIFEST_URL = envConfig.manifestUrl
process.env.__AMAZONQLSP_SUPPORTED_VERSIONS = envConfig.supportedVersions
process.env.__AMAZONQLSP_ID = envConfig.id
process.env.__AMAZONQLSP_LOCATION_OVERRIDE = envConfig.locationOverride

serviceConfigStub.returns(settingConfig)

const config = getAmazonQLspConfig()
assert.deepStrictEqual(config, {
...defaultAmazonQLspConfig,
...envConfig,
})
})
})
2 changes: 1 addition & 1 deletion packages/core/src/shared/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export * from './extensionUtilities'
export * from './extensionStartup'
export { RegionProvider } from './regions/regionProvider'
export { Commands } from './vscode/commands2'
export { getMachineId } from './vscode/env'
export { getMachineId, getServiceEnvVarConfig } from './vscode/env'
export { getLogger } from './logger/logger'
export { activateExtension, openUrl } from './utilities/vsCodeUtils'
export { waitUntil, sleep, Timeout } from './utilities/timeoutUtils'
Expand Down
Loading
Loading