Skip to content

Commit 807aab9

Browse files
committed
support CDK outfile auto detection
1 parent aeba196 commit 807aab9

File tree

7 files changed

+382
-68
lines changed

7 files changed

+382
-68
lines changed

packages/core/src/lambda/remoteDebugging/lambdaDebugger.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ export interface DebugConfig {
3030
lambdaTimeout?: number
3131
layerArn?: string
3232
handlerFile?: string
33+
samProjectLogicalId?: string // SAM project logical ID for auto-detecting outFiles
34+
samProjectRoot?: vscode.Uri // SAM project root for auto-detecting outFiles
3335
isLambdaRemote: boolean // false if LocalStack connection
3436
}
3537

packages/core/src/lambda/remoteDebugging/ldkController.ts

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import { Commands } from '../../shared/vscode/commands2'
2020
import { getLambdaSnapshot, persistLambdaSnapshot, type LambdaDebugger, type DebugConfig } from './lambdaDebugger'
2121
import { RemoteLambdaDebugger } from './remoteLambdaDebugger'
2222
import { LocalStackLambdaDebugger } from './localStackLambdaDebugger'
23+
import { fs } from '../../shared/fs/fs'
24+
import { detectCdkProjects } from '../../awsService/cdk/explorer/detectCdkProjects'
2325

2426
const localize = nls.loadMessageBundle()
2527
const logger = getLogger()
@@ -165,6 +167,108 @@ export async function activateRemoteDebugging(): Promise<void> {
165167
}
166168
}
167169

170+
/**
171+
* Try to auto-detect outFile for TypeScript debugging (SAM or CDK)
172+
* @param debugConfig Debug configuration
173+
* @param functionConfig Lambda function configuration
174+
* @returns The auto-detected outFile path or undefined
175+
*/
176+
export async function tryAutoDetectOutFile(
177+
debugConfig: DebugConfig,
178+
functionConfig: Lambda.FunctionConfiguration
179+
): Promise<string | undefined> {
180+
// Only works for TypeScript files
181+
if (
182+
!debugConfig.handlerFile ||
183+
(!debugConfig.handlerFile.endsWith('.ts') && !debugConfig.handlerFile.endsWith('.tsx'))
184+
) {
185+
return undefined
186+
}
187+
188+
// Try SAM detection first using the provided parameters
189+
if (debugConfig.samProjectLogicalId && debugConfig.samProjectRoot) {
190+
// if proj root is ..../sam-proj/
191+
// build dir will be ..../sam-proj/.aws-sam/build/{LogicalID}/
192+
const samBuildPath = vscode.Uri.joinPath(
193+
debugConfig.samProjectRoot,
194+
'.aws-sam',
195+
'build',
196+
debugConfig.samProjectLogicalId
197+
)
198+
199+
if (await fs.exists(samBuildPath)) {
200+
getLogger().info(`SAM outFile auto-detected: ${samBuildPath.fsPath}`)
201+
return samBuildPath.fsPath
202+
}
203+
}
204+
205+
// If SAM detection didn't work, try CDK detection using the function name
206+
if (!functionConfig.FunctionName) {
207+
return undefined
208+
}
209+
210+
try {
211+
// Find which workspace contains the handler file
212+
const workspaceFolder = vscode.workspace.getWorkspaceFolder(vscode.Uri.file(debugConfig.handlerFile))
213+
if (!workspaceFolder) {
214+
return undefined
215+
}
216+
217+
// Detect CDK projects in the workspace
218+
const cdkProjects = await detectCdkProjects([workspaceFolder])
219+
220+
for (const project of cdkProjects) {
221+
// Check if CDK project contains the handler file
222+
const cdkProjectDir = vscode.Uri.joinPath(project.cdkJsonUri, '..')
223+
if (!debugConfig.handlerFile.startsWith(cdkProjectDir.fsPath)) {
224+
continue
225+
}
226+
227+
// Get the cdk.out directory
228+
const cdkOutDir = vscode.Uri.joinPath(project.treeUri, '..')
229+
230+
// Look for template.json files in cdk.out directory
231+
const pattern = new vscode.RelativePattern(cdkOutDir.fsPath, '*.template.json')
232+
const templateFiles = await vscode.workspace.findFiles(pattern)
233+
234+
for (const templateFile of templateFiles) {
235+
try {
236+
// Read and parse the template.json file
237+
const templateContent = await fs.readFileText(templateFile)
238+
const template = JSON.parse(templateContent)
239+
240+
// Search through resources for a Lambda function with matching FunctionName
241+
for (const [_, resource] of Object.entries(template.Resources || {})) {
242+
const res = resource as any
243+
if (
244+
res.Type === 'AWS::Lambda::Function' &&
245+
res.Properties?.FunctionName === functionConfig.FunctionName
246+
) {
247+
// Found the matching function, extract the asset path from metadata
248+
const assetPath = res.Metadata?.['aws:asset:path']
249+
if (assetPath) {
250+
const assetDir = vscode.Uri.joinPath(cdkOutDir, assetPath)
251+
252+
// Check if the asset directory exists
253+
if (await fs.exists(assetDir)) {
254+
getLogger().info(`CDK outFile auto-detected from template.json: ${assetDir.fsPath}`)
255+
return assetDir.fsPath
256+
}
257+
}
258+
}
259+
}
260+
} catch (error) {
261+
getLogger().debug(`Failed to parse template file ${templateFile.fsPath}: ${error}`)
262+
}
263+
}
264+
}
265+
} catch (error) {
266+
getLogger().warn(`Failed to auto-detect CDK outFile: ${error}`)
267+
}
268+
269+
return undefined
270+
}
271+
168272
/**
169273
* Helper function to check if a string is a valid VSCode glob pattern
170274
*/
@@ -287,6 +391,15 @@ async function getVscodeDebugConfig(
287391
let vsCodeDebugConfig: vscode.DebugConfiguration
288392
switch (debugType) {
289393
case 'node':
394+
// Try to auto-detect outFiles for TypeScript if not provided
395+
if (debugConfig.sourceMap && !debugConfig.outFiles && debugConfig.handlerFile) {
396+
const autoDetectedOutFile = await tryAutoDetectOutFile(debugConfig, functionConfig)
397+
if (autoDetectedOutFile) {
398+
debugConfig.outFiles = [autoDetectedOutFile]
399+
getLogger().info(`outFile auto-detected: ${autoDetectedOutFile}`)
400+
}
401+
}
402+
290403
// source map support
291404
if (debugConfig.sourceMap && debugConfig.outFiles) {
292405
// process outFiles first, if they are relative path (not starting with /),

packages/core/src/lambda/vue/remoteInvoke/invokeLambda.ts

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,6 @@ export class RemoteInvokeWebview extends VueWebview {
151151
private handlerFileAvailable: boolean = false
152152
private isStartingDebug: boolean = false
153153
private handlerFile: string | undefined
154-
private outFile: string | undefined
155154
public constructor(
156155
private readonly channel: vscode.OutputChannel,
157156
private readonly client: LambdaClient,
@@ -412,25 +411,6 @@ export class RemoteInvokeWebview extends VueWebview {
412411
if (watchForUpdates && !isHotReloadingFunction(this.data.LambdaFunctionNode?.configuration.CodeSha256)) {
413412
this.setupFileWatcher()
414413
}
415-
// try auto populate outFile for ts here
416-
if (
417-
(handlerFile.fsPath.endsWith('ts') || handlerFile.fsPath.endsWith('tsx')) &&
418-
this.data.LambdaFunctionNode?.logicalId &&
419-
this.data.LambdaFunctionNode.projectRoot
420-
) {
421-
// if proj root is ..../sam-proj/
422-
// build dir will be ..../sam-proj/.aws-sam/build/{LogicalID}/
423-
const tmpPath = vscode.Uri.joinPath(
424-
this.data.LambdaFunctionNode.projectRoot,
425-
'.aws-sam',
426-
'build',
427-
this.data.LambdaFunctionNode.logicalId
428-
)
429-
if (await fs.exists(tmpPath)) {
430-
this.outFile = tmpPath.fsPath
431-
this.onStateChange.fire({})
432-
}
433-
}
434414
await openLambdaFile(handlerFile.fsPath)
435415
this.handlerFile = handlerFile.fsPath
436416
return true
@@ -651,6 +631,8 @@ export class RemoteInvokeWebview extends VueWebview {
651631
await RemoteDebugController.instance.startDebugging(this.data.FunctionArn, this.data.Runtime ?? 'unknown', {
652632
...config,
653633
handlerFile: this.handlerFile,
634+
samProjectLogicalId: this.data.LambdaFunctionNode.logicalId,
635+
samProjectRoot: this.data.LambdaFunctionNode.projectRoot,
654636
})
655637
} catch (e) {
656638
throw ToolkitError.chain(
@@ -695,10 +677,6 @@ export class RemoteInvokeWebview extends VueWebview {
695677
return this.data.LocalRootPath ?? ''
696678
}
697679

698-
public getOutFile(): string {
699-
return this.outFile ?? ''
700-
}
701-
702680
public getHandlerAvailable(): boolean {
703681
return this.handlerFileAvailable
704682
}

packages/core/src/lambda/vue/remoteInvoke/remoteInvoke.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,8 @@
102102
class="handler-status-message"
103103
>
104104
<info-wrap
105-
>Specify the path to your local directory that contains the handler file for debugging, or
106-
download the handler file from your deployed function.</info-wrap
105+
>Specify the absolute path to your local directory that contains the handler file for
106+
debugging, or download the handler file from your deployed function.</info-wrap
107107
>
108108
</div>
109109
<div v-else style="margin-bottom: 3px" class="handler-status-message">

packages/core/src/lambda/vue/remoteInvoke/remoteInvokeFrontend.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,6 @@ export default defineComponent({
182182
this.debugState.isDebugging = await client.isWebViewDebugging()
183183
this.debugConfig.localRootPath = await client.getLocalPath()
184184
this.debugState.handlerFileAvailable = await client.getHandlerAvailable()
185-
this.runtimeSettings.outFiles = await client.getOutFile()
186185
// Get current session state
187186

188187
if (this.debugState.isDebugging) {

0 commit comments

Comments
 (0)