From 7926d97c335861bb9a9853b63a6ed50803483f16 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 16 Apr 2025 16:13:02 -0700 Subject: [PATCH] fix(lsp): verify that nodejs runs (again) Problem: The logic added in a26437735b08 wasn't added in the other "lsp startup" module. Solution: Add it to the new module also. --- packages/amazonq/src/lsp/client.ts | 29 +++++++--- packages/core/src/amazonq/lsp/lspClient.ts | 55 +------------------ .../core/src/shared/lsp/utils/platform.ts | 49 +++++++++++++++++ 3 files changed, 73 insertions(+), 60 deletions(-) diff --git a/packages/amazonq/src/lsp/client.ts b/packages/amazonq/src/lsp/client.ts index 3d26d1368f1..84079ad3b77 100644 --- a/packages/amazonq/src/lsp/client.ts +++ b/packages/amazonq/src/lsp/client.ts @@ -11,11 +11,21 @@ import { InlineCompletionManager } from '../app/inline/completion' import { AmazonQLspAuth, encryptionKey, notificationTypes } from './auth' import { AuthUtil } from 'aws-core-vscode/codewhisperer' import { ConnectionMetadata } from '@aws/language-server-runtimes/protocol' -import { Settings, oidcClientName, createServerOptions, globals, Experiments, Commands } from 'aws-core-vscode/shared' +import { + Settings, + oidcClientName, + createServerOptions, + globals, + Experiments, + Commands, + validateNodeExe, + getLogger, +} from 'aws-core-vscode/shared' import { activate } from './chat/activation' import { AmazonQResourcePaths } from './lspInstaller' const localize = nls.loadMessageBundle() +const logger = getLogger('amazonqLsp.lspClient') export async function startLanguageServer( extensionContext: vscode.ExtensionContext, @@ -25,17 +35,18 @@ export async function startLanguageServer( const serverModule = resourcePaths.lsp + const argv = [ + '--nolazy', + '--preserve-symlinks', + '--stdio', + '--pre-init-encryption', + '--set-credentials-encryption-key', + ] const serverOptions = createServerOptions({ encryptionKey, executable: resourcePaths.node, serverModule, - execArgv: [ - '--nolazy', - '--preserve-symlinks', - '--stdio', - '--pre-init-encryption', - '--set-credentials-encryption-key', - ], + execArgv: argv, }) const documentSelector = [{ scheme: 'file', language: '*' }] @@ -43,6 +54,8 @@ export async function startLanguageServer( const clientId = 'amazonq' const traceServerEnabled = Settings.instance.isSet(`${clientId}.trace.server`) + await validateNodeExe(resourcePaths.node, resourcePaths.lsp, argv, logger) + // Options to control the language client const clientOptions: LanguageClientOptions = { // Register the server for json documents diff --git a/packages/core/src/amazonq/lsp/lspClient.ts b/packages/core/src/amazonq/lsp/lspClient.ts index 767af8dd5ee..b992fef2fe3 100644 --- a/packages/core/src/amazonq/lsp/lspClient.ts +++ b/packages/core/src/amazonq/lsp/lspClient.ts @@ -39,15 +39,13 @@ import { fs } from '../../shared/fs/fs' import { getLogger } from '../../shared/logger/logger' import globals from '../../shared/extensionGlobals' import { ResourcePaths } from '../../shared/lsp/types' -import { createServerOptions } from '../../shared/lsp/utils/platform' +import { createServerOptions, validateNodeExe } from '../../shared/lsp/utils/platform' import { waitUntil } from '../../shared/utilities/timeoutUtils' -import { ToolkitError } from '../../shared/errors' -import { ChildProcess } from '../../shared/utilities/processUtils' const localize = nls.loadMessageBundle() const key = crypto.randomBytes(32) -const logger = getLogger('amazonqLsp.lspClient') +const logger = getLogger('amazonqWorkspaceLsp') /** * LspClient manages the API call between VS Code extension and LSP server @@ -226,53 +224,6 @@ export class LspClient { } } -/** - * Checks that we can actually run the `node` executable and execute code with it. - */ -async function validateNodeExe(nodePath: string, lsp: string, args: string[]) { - // Check that we can start `node` by itself. - const proc = new ChildProcess(nodePath, ['-e', 'console.log("ok " + process.version)'], { logging: 'no' }) - const r = await proc.run() - const ok = r.exitCode === 0 && r.stdout.includes('ok') - if (!ok) { - const msg = `failed to run basic "node -e" test (exitcode=${r.exitCode}): ${proc.toString(false, true)}` - logger.error(msg) - throw new ToolkitError(`amazonqLsp: ${msg}`) - } - - // Check that we can start `node …/lsp.js --stdio …`. - const lspProc = new ChildProcess(nodePath, [lsp, ...args], { logging: 'no' }) - try { - // Start asynchronously (it never stops; we need to stop it below). - lspProc.run().catch((e) => logger.error('failed to run: %s', lspProc.toString(false, true))) - - const ok2 = - !lspProc.stopped && - (await waitUntil( - async () => { - return lspProc.pid() !== undefined - }, - { - timeout: 5000, - interval: 100, - truthy: true, - } - )) - const selfExit = await waitUntil(async () => lspProc.stopped, { - timeout: 500, - interval: 100, - truthy: true, - }) - if (!ok2 || selfExit) { - throw new ToolkitError( - `amazonqLsp: failed to run (exitcode=${lspProc.exitCode()}): ${lspProc.toString(false, true)}` - ) - } - } finally { - lspProc.stop(true) - } -} - /** * Activates the language server (assumes the LSP server has already been downloaded): * 1. start LSP server running over IPC protocol. @@ -312,7 +263,7 @@ export async function activate(extensionContext: ExtensionContext, resourcePaths const documentSelector = [{ scheme: 'file', language: '*' }] - await validateNodeExe(resourcePaths.node, resourcePaths.lsp, debugOptions.execArgv) + await validateNodeExe(resourcePaths.node, resourcePaths.lsp, debugOptions.execArgv, logger) // Options to control the language client const clientOptions: LanguageClientOptions = { diff --git a/packages/core/src/shared/lsp/utils/platform.ts b/packages/core/src/shared/lsp/utils/platform.ts index 4d242c3833e..5d96ef496f4 100644 --- a/packages/core/src/shared/lsp/utils/platform.ts +++ b/packages/core/src/shared/lsp/utils/platform.ts @@ -4,7 +4,9 @@ */ import { ToolkitError } from '../../errors' +import { Logger } from '../../logger/logger' import { ChildProcess } from '../../utilities/processUtils' +import { waitUntil } from '../../utilities/timeoutUtils' import { isDebugInstance } from '../../vscode/env' export function getNodeExecutableName(): string { @@ -24,6 +26,53 @@ function getEncryptionInit(key: Buffer): string { return JSON.stringify(request) + '\n' } +/** + * Checks that we can actually run the `node` executable and execute code with it. + */ +export async function validateNodeExe(nodePath: string, lsp: string, args: string[], logger: Logger) { + // Check that we can start `node` by itself. + const proc = new ChildProcess(nodePath, ['-e', 'console.log("ok " + process.version)'], { logging: 'no' }) + const r = await proc.run() + const ok = r.exitCode === 0 && r.stdout.includes('ok') + if (!ok) { + const msg = `failed to run basic "node -e" test (exitcode=${r.exitCode}): ${proc.toString(false, true)}` + logger.error(msg) + throw new ToolkitError(`amazonqLsp: ${msg}`) + } + + // Check that we can start `node …/lsp.js --stdio …`. + const lspProc = new ChildProcess(nodePath, [lsp, ...args], { logging: 'no' }) + try { + // Start asynchronously (it never stops; we need to stop it below). + lspProc.run().catch((e) => logger.error('failed to run: %s', lspProc.toString(false, true))) + + const ok2 = + !lspProc.stopped && + (await waitUntil( + async () => { + return lspProc.pid() !== undefined + }, + { + timeout: 5000, + interval: 100, + truthy: true, + } + )) + const selfExit = await waitUntil(async () => lspProc.stopped, { + timeout: 500, + interval: 100, + truthy: true, + }) + if (!ok2 || selfExit) { + throw new ToolkitError( + `amazonqLsp: failed to run (exitcode=${lspProc.exitCode()}): ${lspProc.toString(false, true)}` + ) + } + } finally { + lspProc.stop(true) + } +} + export function createServerOptions({ encryptionKey, executable,