Skip to content

Commit 195af23

Browse files
Merge master into feature/smus
2 parents 3e41d93 + 1de831b commit 195af23

File tree

7 files changed

+218
-20
lines changed

7 files changed

+218
-20
lines changed

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

Lines changed: 71 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -280,33 +280,86 @@ function isVscodeGlob(pattern: string): boolean {
280280
return /[*?[\]{}]/.test(pattern)
281281
}
282282

283+
/**
284+
* Extract temp directory patterns from source map files
285+
* @param mapFiles Array of source map file paths
286+
* @returns Set of temp directory patterns found in source maps
287+
*/
288+
async function extractTempPatternsFromSourceMaps(mapFiles: string[]): Promise<Set<string>> {
289+
const tempPatterns = new Set<string>()
290+
291+
for (const mapFile of mapFiles) {
292+
try {
293+
const content = await fs.readFileText(mapFile)
294+
const sourceMap = JSON.parse(content)
295+
296+
if (sourceMap.sources && Array.isArray(sourceMap.sources)) {
297+
for (const source of sourceMap.sources) {
298+
// SAM uses Python's tempfile.mkdtemp() to create tmp dir which we want to detect
299+
// tempfile.mkdtemp() uses lowercase letters, digits, and underscores
300+
// The pattern is: tmp followed by 8 characters from [a-z0-9_]
301+
// see https://github.com/python/cpython/blob/20a677d75a95fa63be904f7ca4f8cb268aec95c1/Lib/tempfile.py#L132-L140
302+
const tempMatch = source.match(/\btmp[a-z0-9_]{8}\b/)
303+
if (tempMatch) {
304+
tempPatterns.add(tempMatch[0])
305+
getLogger().debug(`Found temp pattern in source map: ${tempMatch[0]}`)
306+
}
307+
}
308+
}
309+
} catch (error) {
310+
getLogger().debug(`Failed to read or parse source map ${mapFile}: ${error}`)
311+
}
312+
}
313+
314+
return tempPatterns
315+
}
316+
283317
/**
284318
* Helper function to validate source map files exist for given outFiles patterns
319+
* @returns Object with validation result and temp patterns found in source maps
285320
*/
286-
async function validateSourceMapFiles(outFiles: string[]): Promise<boolean> {
321+
export async function validateSourceMapFiles(
322+
outFiles: string[]
323+
): Promise<{ isValid: boolean; tempPatterns: Set<string> }> {
324+
getLogger().debug(`validating outFiles ${outFiles}`)
287325
const allAreGlobs = outFiles.every((pattern) => isVscodeGlob(pattern))
288326
if (!allAreGlobs) {
289-
return false
327+
return { isValid: false, tempPatterns: new Set() }
290328
}
291329

292330
try {
293331
let jsfileCount = 0
294332
let mapfileCount = 0
295-
const jsFiles = await glob(outFiles, { ignore: 'node_modules/**' })
333+
const mapFiles: string[] = []
334+
335+
// Convert Windows paths to use forward slashes for glob
336+
const normalizedOutFiles = outFiles.map((pattern) => {
337+
// Replace backslashes with forward slashes for glob compatibility
338+
return pattern.replaceAll(/\\/g, '/')
339+
})
340+
getLogger().debug(`normalizedOutFiles ${normalizedOutFiles}`)
341+
const jsFiles = await glob(normalizedOutFiles, { ignore: 'node_modules/**' })
296342

297343
for (const file of jsFiles) {
298344
if (file.includes('js')) {
299345
jsfileCount += 1
300346
}
301347
if (file.includes('.map')) {
302348
mapfileCount += 1
349+
mapFiles.push(file)
303350
}
304351
}
305352

306-
return jsfileCount === 0 || mapfileCount === 0 ? false : true
353+
// Extract temp patterns from source map files
354+
const tempPatterns = await extractTempPatternsFromSourceMaps(mapFiles)
355+
356+
return {
357+
isValid: jsfileCount > 0 && mapfileCount > 0,
358+
tempPatterns,
359+
}
307360
} catch (error) {
308361
getLogger().warn(`Error validating source map files: ${error}`)
309-
return false
362+
return { isValid: false, tempPatterns: new Set() }
310363
}
311364
}
312365

@@ -412,19 +465,26 @@ async function getVscodeDebugConfig(
412465
debugConfig.outFiles = processOutFiles(debugConfig.outFiles, debugConfig.localRoot)
413466

414467
// Use glob to search if there are any matching js file or source map file
415-
const hasSourceMaps = await validateSourceMapFiles(debugConfig.outFiles)
468+
const sourceMapValidation = await validateSourceMapFiles(debugConfig.outFiles)
416469

417-
if (hasSourceMaps) {
418-
// support mapping common sam cli location
419-
additionalParams['sourceMapPathOverrides'] = {
470+
if (sourceMapValidation.isValid) {
471+
// Start with basic source map overrides
472+
const sourceMapOverrides: Record<string, string> = {
420473
...additionalParams['sourceMapPathOverrides'],
421-
'?:*/T/?:*/*': path.join(debugConfig.localRoot, '*'),
422474
}
475+
476+
// Add specific temp directory patterns found in source maps
477+
for (const tempPattern of sourceMapValidation.tempPatterns) {
478+
sourceMapOverrides[`?:*/${tempPattern}/*`] = path.join(debugConfig.localRoot, '*')
479+
getLogger().info(`Added source map override for temp pattern: ${tempPattern}`)
480+
}
481+
482+
additionalParams['sourceMapPathOverrides'] = sourceMapOverrides
423483
debugConfig.localRoot = debugConfig.outFiles[0].split('*')[0]
424484
} else {
425485
debugConfig.sourceMap = false
426486
debugConfig.outFiles = undefined
427-
await showMessage(
487+
void showMessage(
428488
'warn',
429489
localize(
430490
'AWS.lambda.remoteDebug.outFileNotFound',

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

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -283,17 +283,28 @@ export class RemoteInvokeWebview extends VueWebview {
283283
this.channel.appendLine('Loading response...')
284284
await telemetry.lambda_invokeRemote.run(async (span) => {
285285
try {
286-
const funcResponse = remoteDebugEnabled
287-
? await this.clientDebug.invoke(this.data.FunctionArn, input, qualifier)
288-
: await this.client.invoke(this.data.FunctionArn, input, qualifier)
286+
let funcResponse
287+
const snapStartDisabled =
288+
!this.data.LambdaFunctionNode?.configuration.SnapStart &&
289+
this.data.LambdaFunctionNode?.configuration.State !== 'Active'
290+
if (remoteDebugEnabled) {
291+
funcResponse = await this.clientDebug.invoke(this.data.FunctionArn, input, qualifier)
292+
} else if (snapStartDisabled) {
293+
funcResponse = await this.client.invoke(this.data.FunctionArn, input, qualifier, 'None')
294+
} else {
295+
funcResponse = await this.client.invoke(this.data.FunctionArn, input, qualifier, 'Tail')
296+
}
297+
289298
const logs = funcResponse.LogResult ? decodeBase64(funcResponse.LogResult) : ''
290299
const decodedPayload = funcResponse.Payload ? new TextDecoder().decode(funcResponse.Payload) : ''
291300
const payload = decodedPayload || JSON.stringify({})
292301

293302
this.channel.appendLine(`Invocation result for ${this.data.FunctionArn}`)
294-
this.channel.appendLine('Logs:')
295-
this.channel.appendLine(logs)
296-
this.channel.appendLine('')
303+
if (!snapStartDisabled) {
304+
this.channel.appendLine('Logs:')
305+
this.channel.appendLine(logs)
306+
this.channel.appendLine('')
307+
}
297308
this.channel.appendLine('Payload:')
298309
this.channel.appendLine(String(payload))
299310
this.channel.appendLine('')

packages/core/src/shared/clients/lambdaClient.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,18 @@ export class DefaultLambdaClient {
6666
)
6767
}
6868

69-
public async invoke(name: string, payload?: BlobPayloadInputTypes, version?: string): Promise<InvocationResponse> {
69+
public async invoke(
70+
name: string,
71+
payload?: BlobPayloadInputTypes,
72+
version?: string,
73+
logtype: 'Tail' | 'None' = 'Tail'
74+
): Promise<InvocationResponse> {
7075
const sdkClient = await this.createSdkClient()
7176

7277
const response = await sdkClient.send(
7378
new InvokeCommand({
7479
FunctionName: name,
75-
LogType: 'Tail',
80+
LogType: logtype,
7681
Payload: payload,
7782
Qualifier: version,
7883
})

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

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
activateRemoteDebugging,
1313
revertExistingConfig,
1414
tryAutoDetectOutFile,
15+
validateSourceMapFiles,
1516
} from '../../../lambda/remoteDebugging/ldkController'
1617
import { getLambdaSnapshot, type DebugConfig } from '../../../lambda/remoteDebugging/lambdaDebugger'
1718
import { LdkClient } from '../../../lambda/remoteDebugging/ldkClient'
@@ -32,6 +33,7 @@ import {
3233
import { getRemoteDebugLayer } from '../../../lambda/remoteDebugging/remoteLambdaDebugger'
3334
import { fs } from '../../../shared/fs/fs'
3435
import * as detectCdkProjects from '../../../awsService/cdk/explorer/detectCdkProjects'
36+
import * as glob from 'glob'
3537

3638
describe('RemoteDebugController', () => {
3739
let sandbox: sinon.SinonSandbox
@@ -648,6 +650,94 @@ describe('tryAutoDetectOutFile', () => {
648650
})
649651
})
650652

653+
describe('Source Map Pattern Extraction', () => {
654+
let sandbox: sinon.SinonSandbox
655+
656+
beforeEach(() => {
657+
sandbox = sinon.createSandbox()
658+
})
659+
660+
afterEach(() => {
661+
sandbox.restore()
662+
})
663+
664+
it('should extract temp patterns from source map files', async () => {
665+
assert(vscode.workspace.workspaceFolders?.[0]?.uri, 'Test env should have a workdir')
666+
const testPath = vscode.Uri.joinPath(vscode.workspace.workspaceFolders?.[0]?.uri, 'remote-debug-ts-app').fsPath
667+
668+
// Call validateSourceMapFiles which will extract temp patterns
669+
const result = await validateSourceMapFiles([`${testPath}/*`])
670+
671+
assert(result.isValid, 'Should find valid source map files')
672+
assert(result.tempPatterns.has('tmp5bmwuffn'), 'Should extract temp pattern tmp5bmwuffn from source map')
673+
})
674+
675+
it('should handle multiple temp patterns in source maps', async () => {
676+
// Create a mock source map with multiple temp patterns
677+
// Updated to use lowercase and underscore patterns matching Python's tempfile.mkdtemp()
678+
const mockSourceMap = {
679+
version: 3,
680+
file: 'index.js',
681+
sources: [
682+
'../../../../../../tmpa1b2c3d4/index.ts',
683+
'../../../../../../tmpx9y8_7w6/utils.ts',
684+
'../../../../../../tmp_test123/helper.ts',
685+
'/var/task/regular-path.ts', // This should not match
686+
],
687+
mappings: 'AAAA',
688+
}
689+
690+
// Mock fs.readFileText to return our mock source map
691+
sandbox.stub(fs, 'readFileText').resolves(JSON.stringify(mockSourceMap))
692+
693+
// Call extractTempPatternsFromSourceMaps directly (we need to export it first)
694+
// For now, we'll test through validateSourceMapFiles
695+
696+
sandbox.stub(glob, 'glob').resolves(['/test/path/index.js', '/test/path/index.js.map'])
697+
698+
const result = await validateSourceMapFiles(['/test/path/*'])
699+
700+
assert(result.isValid, 'Should be valid')
701+
assert(result.tempPatterns.has('tmpa1b2c3d4'), 'Should extract first temp pattern')
702+
assert(result.tempPatterns.has('tmpx9y8_7w6'), 'Should extract second temp pattern')
703+
assert(result.tempPatterns.has('tmp_test123'), 'Should extract third temp pattern')
704+
assert.strictEqual(result.tempPatterns.size, 3, 'Should have exactly 3 temp patterns')
705+
})
706+
707+
it('should handle source maps without temp patterns', async () => {
708+
// Create a mock source map without temp patterns
709+
const mockSourceMap = {
710+
version: 3,
711+
file: 'index.js',
712+
sources: ['./index.ts', '../utils/helper.ts'],
713+
mappings: 'AAAA',
714+
}
715+
716+
// Mock fs.readFileText
717+
sandbox.stub(fs, 'readFileText').resolves(JSON.stringify(mockSourceMap))
718+
719+
// Mock glob
720+
sandbox.stub(glob, 'glob').resolves(['/test/path/index.js', '/test/path/index.js.map'])
721+
722+
const result = await validateSourceMapFiles(['/test/path/*'])
723+
724+
assert(result.isValid, 'Should be valid even without temp patterns')
725+
assert.strictEqual(result.tempPatterns.size, 0, 'Should have no temp patterns')
726+
})
727+
728+
it('should handle malformed source map files gracefully', async () => {
729+
// Mock fs.readFileText to return invalid JSON
730+
sandbox.stub(fs, 'readFileText').resolves('{ invalid json }')
731+
732+
sandbox.stub(glob, 'glob').resolves(['/test/path/index.js', '/test/path/index.js.map'])
733+
734+
const result = await validateSourceMapFiles(['/test/path/*'])
735+
736+
assert(result.isValid, 'Should still be valid despite malformed source map')
737+
assert.strictEqual(result.tempPatterns.size, 0, 'Should have no temp patterns when parsing fails')
738+
})
739+
})
740+
651741
describe('Module Functions', () => {
652742
let sandbox: sinon.SinonSandbox
653743
let mockGlobalState: any

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ describe('RemoteInvokeWebview', () => {
4242
FunctionArn: 'arn:aws:lambda:us-west-2:123456789012:function:testFunction',
4343
FunctionRegion: 'us-west-2',
4444
InputSamples: [],
45+
LambdaFunctionNode: {
46+
configuration: {
47+
State: 'Active',
48+
},
49+
} as LambdaFunctionNode,
4550
} as InitialData
4651

4752
remoteInvokeWebview = new RemoteInvokeWebview(outputChannel, client, client, data)
@@ -53,6 +58,11 @@ describe('RemoteInvokeWebview', () => {
5358
FunctionArn: 'arn:aws:lambda:us-west-2:123456789012:function:testFunction',
5459
FunctionRegion: 'us-west-2',
5560
InputSamples: [],
61+
LambdaFunctionNode: {
62+
configuration: {
63+
State: 'Active',
64+
},
65+
} as LambdaFunctionNode,
5666
}
5767
const result = remoteInvokeWebview.init()
5868
assert.deepEqual(result, mockData)
@@ -74,7 +84,7 @@ describe('RemoteInvokeWebview', () => {
7484

7585
await remoteInvokeWebview.invokeLambda(input)
7686
assert(client.invoke.calledOnce)
77-
assert(client.invoke.calledWith(data.FunctionArn, input))
87+
assert(client.invoke.calledWith(data.FunctionArn, input, sinon.match.any, 'Tail'))
7888
assert.deepStrictEqual(appendedLines, [
7989
'Loading response...',
8090
'Invocation result for arn:aws:lambda:us-west-2:123456789012:function:testFunction',

packages/core/src/testFixtures/workspaceFolder/remote-debug-ts-app/handler.js

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/core/src/testFixtures/workspaceFolder/remote-debug-ts-app/handler.js.map

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)