Skip to content

feat(toolkit): add cdk lsp - #8828

Open
megha-narayanan wants to merge 3 commits into
aws:masterfrom
megha-narayanan:feat/cdk-lsp
Open

feat(toolkit): add cdk lsp#8828
megha-narayanan wants to merge 3 commits into
aws:masterfrom
megha-narayanan:feat/cdk-lsp

Conversation

@megha-narayanan

Copy link
Copy Markdown

The CDK CLI now ships a language server (cdk lsp, added in aws-cdk 2.1132.0) that links construct source, the synthesized template, and policy violations. This PR starts cdk lsp from the user's own CDK CLI as a stdio language client for the CDK app in the workspace.

Design:

  • Discover the CLI in order: aws.cdk.cliPath setting, workspace node_modules/.bin/cdk, then PATH. Local first, so the server matches the version the project synths with.
  • Require cdk >= 2.1132.0 (the first release with cdk lsp). Older or missing CLIs get a dismissible prompt instead of a broken start.
  • Gate activation on detectCdkProjects so nothing spawns outside a CDK workspace, and resolve a login-shell PATH via resolveEnv so the server's
    synth subprocess finds node and java. Credentials are not injected.
  • Run one client per window; aws.cdk.appDir selects the app when a workspace has more than one. A single client avoids the duplicate executeCommandregistration that multiple clients would hit.

  • Treat all work as PUBLIC. Private feature/x branches will not be squash-merged at release time.
  • Your code changes must meet the guidelines in CONTRIBUTING.md.
  • License: I confirm that my contribution is made under the terms of the Apache 2.0 license.

…K CLI

Discover the user's `cdk` CLI (aws.cdk.cliPath setting, else workspace
node_modules, else shell PATH) and start `cdk lsp` over stdio for the CDK app
in the workspace. Gated on cdk >= 2.1132.0, the first release carrying the lsp
command. Reuse detectCdkProjects for the activation gate and resolveEnv for a
login-shell PATH so the server's synth subprocess resolves node/java.

Run a single client per window (aws.cdk.appDir selects the app when several are
present), since each client re-registers the server's executeCommand ids and a
duplicate registration throws. Emit cdk_startLanguageServer telemetry on
success and failure.
Overlapping restart triggers (activation, config/folder changes, a new
cdk.json) could run two starts concurrently and leak a client whose reference
was overwritten. Serialize restarts and coalesce bursts into one trailing
restart. Add the changelog entry for the feature.
@amazon-inspector-ohio

Copy link
Copy Markdown

⏳ I'm reviewing this pull request for security vulnerabilities and code quality issues. I'll provide an update when I'm done

@github-actions

Copy link
Copy Markdown
  • This pull request modifies code in src/* but no tests were added/updated.
    • Confirm whether tests should be added or ensure the PR description explains why tests are not required.

@amazon-inspector-ohio

Copy link
Copy Markdown

✅ I finished the code review, and didn't find any security or code quality issues.

@megha-narayanan megha-narayanan changed the title feat: add cdk lsp feat(toolkit): add cdk lsp Jul 17, 2026
@megha-narayanan
megha-narayanan marked this pull request as ready for review July 17, 2026 18:07
@megha-narayanan
megha-narayanan requested a review from a team July 17, 2026 18:07

@rix0rrr rix0rrr left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have some trivial questions on details of the subcommand handling, but overall I support this change. Looks good and I like the edge case handling of file changes that is done.

Provisionally approved from my end

const merged = await mergeResolvedShellPath(process.env)
const env: NodeJS.ProcessEnv = { ...merged }

const resolved = await getResolvedShellEnv(process.env)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is taking this environment wholesale not the right solution?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point, switched to taking the resolved shell env instead of cherry-picking JAVA_HOME.

/** `which`-style scan of env.PATH for an executable. */
async function findOnPath(name: string, env: NodeJS.ProcessEnv): Promise<string | undefined> {
const rawPath = env.PATH ?? process.env.PATH ?? ''
const exts = process.platform === 'win32' ? ['.cmd', '.exe', '.bat', ''] : ['']

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit inconsistent. If you search for both .bat and .cmd here, I feel you should do the same for findNodeModules

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

node_modules is npm-controlled and npm only ever writes a .cmd shim there (plus a .ps1 and an extensionless POSIX shell script), and .cmd is the only one Node can spawn on Windows. So cdk.cmd is the sole valid candidate, and looking for .bat/.exe there would be dead code.

findOnPath scans an open-ended PATH where a cdk could come from other installers, so it casts a wider net. The one thing that was genuinely wrong is that I had .bat in that list, and npm never produces a cdk.bat, so I dropped it. The PATH list is now .cmd, .exe, and extensionless, which covers the npm shim, a native binary, and a POSIX cdk respectively.

Both lists now carry a one-line comment explaining the difference so it doesn't look like an oversight.

*/
export async function probeCdkVersion(command: string, env: NodeJS.ProcessEnv): Promise<string | undefined> {
try {
const proc = new ChildProcess(command, ['--version'], { spawnOptions: { env }, collect: true })

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this work on Windows if the target is a .cmd file? (That needs the shell)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this call it's fine sinceprobeCdkVersion goes through our ChildProcess wrapper, which spawns via cross-spawn, so a .cmd target on Windows is handled. But the actual server start in client.ts uses vscode-languageclient's Executable, and I realized that path does a plain child_process.spawn with no shell, so a cdk.cmd command would fail to launch on Windows even though the probe passed. Just fixed at the source shell: true on Windows for the Executable, with the command path quoted so a path with spaces still parses.

- buildCdkSpawnEnv: take the resolved shell env; host env and unioned PATH
  win collisions, generalizing the JAVA_HOME gap-fill to all shell vars
- findOnPath: drop .bat (npm never produces one); comment why the PATH and
  node_modules extension lists differ
- lsp client: spawn the cdk .cmd through a shell on Windows with a quoted
  command path, since vscode-languageclient uses raw child_process.spawn
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants