@@ -17,6 +17,8 @@ import { samDeployUrl } from '../../shared/constants'
1717import path from 'path'
1818import fs from '../../shared/fs/fs'
1919import { getLogger } from '../../shared/logger/logger'
20+ import { RuntimeFamily , getFamily } from '../../lambda/models/samLambdaRuntime'
21+ import { showMessage } from '../../shared/utilities/messages'
2022const localize = nls . loadMessageBundle ( )
2123
2224export async function runOpenTemplate ( arg ?: TreeNode ) {
@@ -28,36 +30,134 @@ export async function runOpenTemplate(arg?: TreeNode) {
2830 await vscode . window . showTextDocument ( document )
2931}
3032
31- export async function runOpenHandler ( arg : ResourceNode ) {
33+ /**
34+ * Find and open the lambda handler with given ResoruceNode
35+ * If not found, a NoHandlerFound error will be raised
36+ * @param arg ResourceNode
37+ */
38+ export async function runOpenHandler ( arg : ResourceNode ) : Promise < void > {
3239 const folderUri = path . dirname ( arg . resource . location . fsPath )
33- let handler : string | undefined
34- let extension = '*'
35- if ( arg . resource . resource . Runtime ?. includes ( 'java' ) ) {
36- handler = arg . resource . resource . Handler ?. split ( '::' ) [ 0 ]
37- if ( handler ?. includes ( '.' ) ) {
38- handler = handler . split ( '.' ) [ 1 ]
39- }
40- extension = 'java'
41- } else if ( arg . resource . resource . Runtime ?. includes ( 'dotnet' ) ) {
42- handler = arg . resource . resource . Handler ?. split ( '::' ) [ 1 ]
43- if ( handler ?. includes ( '.' ) ) {
44- handler = handler . split ( '.' ) [ 1 ]
45- }
46- extension = 'cs'
47- } else {
48- handler = arg . resource . resource . Handler ?. split ( '.' ) [ 0 ]
40+ if ( ! arg . resource . resource . CodeUri ) {
41+ throw new ToolkitError ( 'No CodeUri provided in template, cannot open handler' , { code : 'NoCodeUriProvided' } )
4942 }
50- const handlerFile = (
51- await vscode . workspace . findFiles (
52- new vscode . RelativePattern ( folderUri , `**/${ handler } .${ extension } ` ) ,
53- new vscode . RelativePattern ( folderUri , '.aws-sam' )
54- )
55- ) [ 0 ]
43+
44+ if ( ! arg . resource . resource . Handler ) {
45+ throw new ToolkitError ( 'No Handler provided in template, cannot open handler' , { code : 'NoHandlerProvided' } )
46+ }
47+
48+ if ( ! arg . resource . resource . Runtime ) {
49+ throw new ToolkitError ( 'No Runtime provided in template, cannot open handler' , { code : 'NoRuntimeProvided' } )
50+ }
51+
52+ const handlerFile = await getLambdaHandlerFile (
53+ vscode . Uri . file ( folderUri ) ,
54+ arg . resource . resource . CodeUri ,
55+ arg . resource . resource . Handler ,
56+ arg . resource . resource . Runtime
57+ )
5658 if ( ! handlerFile ) {
57- throw new ToolkitError ( `No handler file found with name "${ handler } "` , { code : 'NoHandlerFound' } )
59+ throw new ToolkitError ( `No handler file found with name "${ arg . resource . resource . Handler } "` , {
60+ code : 'NoHandlerFound' ,
61+ } )
5862 }
59- const document = await vscode . workspace . openTextDocument ( handlerFile )
60- await vscode . window . showTextDocument ( document )
63+ await vscode . workspace . openTextDocument ( handlerFile ) . then ( async ( doc ) => await vscode . window . showTextDocument ( doc ) )
64+ }
65+
66+ // create a set to store all supported runtime in the following function
67+ const supportedRuntimeForHandler = new Set < RuntimeFamily > ( [
68+ RuntimeFamily . Ruby ,
69+ RuntimeFamily . Python ,
70+ RuntimeFamily . NodeJS ,
71+ RuntimeFamily . DotNet ,
72+ RuntimeFamily . Java ,
73+ ] )
74+
75+ /**
76+ * Get the actual Lambda handler file, in vscode.Uri format, from the template
77+ * file and handler name. If not found, return undefined.
78+ *
79+ * @param folderUri The root folder for sam project
80+ * @param codeUri codeUri prop in sam template
81+ * @param handler handler prop in sam template
82+ * @param runtime runtime prop in sam template
83+ * @returns
84+ */
85+ export async function getLambdaHandlerFile (
86+ folderUri : vscode . Uri ,
87+ codeUri : string ,
88+ handler : string ,
89+ runtime : string
90+ ) : Promise < vscode . Uri | undefined > {
91+ const family = getFamily ( runtime )
92+ if ( ! supportedRuntimeForHandler . has ( family ) ) {
93+ throw new ToolkitError ( `Runtime ${ runtime } is not supported for open handler button` , {
94+ code : 'RuntimeNotSupported' ,
95+ } )
96+ }
97+
98+ const handlerParts = handler . split ( '.' )
99+ // sample: app.lambda_handler -> app.rb
100+ if ( family === RuntimeFamily . Ruby ) {
101+ // Ruby supports namespace/class handlers as well, but the path is
102+ // guaranteed to be slash-delimited so we can assume the first part is
103+ // the path
104+ return vscode . Uri . joinPath ( folderUri , codeUri , handlerParts . slice ( 0 , handlerParts . length - 1 ) . join ( '/' ) + '.rb' )
105+ }
106+
107+ // sample:app.lambda_handler -> app.py
108+ if ( family === RuntimeFamily . Python ) {
109+ // Otherwise (currently Node.js and Python) handle dot-delimited paths
110+ return vscode . Uri . joinPath ( folderUri , codeUri , handlerParts . slice ( 0 , handlerParts . length - 1 ) . join ( '/' ) + '.py' )
111+ }
112+
113+ // sample: app.handler -> app.mjs/app.js
114+ // More likely to be mjs if NODEJS version>=18, now searching for both
115+ if ( family === RuntimeFamily . NodeJS ) {
116+ const handlerName = handlerParts . slice ( 0 , handlerParts . length - 1 ) . join ( '/' )
117+ const handlerPath = path . dirname ( handlerName )
118+ const handlerFile = path . basename ( handlerName )
119+ const pattern = new vscode . RelativePattern (
120+ vscode . Uri . joinPath ( folderUri , codeUri , handlerPath ) ,
121+ `${ handlerFile } .{js,mjs}`
122+ )
123+ return searchHandlerFile ( folderUri , pattern )
124+ }
125+ // search directly under Code uri for Dotnet and java
126+ // sample: ImageResize::ImageResize.Function::FunctionHandler -> Function.cs
127+ if ( family === RuntimeFamily . DotNet ) {
128+ const handlerName = path . basename ( handler . split ( '::' ) [ 1 ] . replaceAll ( '.' , '/' ) )
129+ const pattern = new vscode . RelativePattern ( vscode . Uri . joinPath ( folderUri , codeUri ) , `${ handlerName } .cs` )
130+ return searchHandlerFile ( folderUri , pattern )
131+ }
132+
133+ // sample: resizer.App::handleRequest -> App.java
134+ if ( family === RuntimeFamily . Java ) {
135+ const handlerName = handler . split ( '::' ) [ 0 ] . replaceAll ( '.' , '/' )
136+ const pattern = new vscode . RelativePattern ( vscode . Uri . joinPath ( folderUri , codeUri ) , `**/${ handlerName } .java` )
137+ return searchHandlerFile ( folderUri , pattern )
138+ }
139+ }
140+
141+ /**
142+ Searches for a handler file in the given pattern and returns the first match.
143+ If no match is found, returns undefined.
144+ */
145+ export async function searchHandlerFile (
146+ folderUri : vscode . Uri ,
147+ pattern : vscode . RelativePattern
148+ ) : Promise < vscode . Uri | undefined > {
149+ const handlerFile = await vscode . workspace . findFiles ( pattern , new vscode . RelativePattern ( folderUri , '.aws-sam' ) )
150+ if ( handlerFile . length === 0 ) {
151+ return undefined
152+ }
153+ if ( handlerFile . length > 1 ) {
154+ getLogger ( ) . warn ( `Multiple handler files found with name "${ path . basename ( handlerFile [ 0 ] . fsPath ) } "` )
155+ void showMessage ( 'warn' , `Multiple handler files found with name "${ path . basename ( handlerFile [ 0 ] . fsPath ) } "` )
156+ }
157+ if ( await fs . exists ( handlerFile [ 0 ] ) ) {
158+ return handlerFile [ 0 ]
159+ }
160+ return undefined
61161}
62162
63163async function promptUserForTemplate ( ) {
0 commit comments