@@ -30,6 +30,8 @@ import {
3030import enumList from './enum' ;
3131
3232const delay = time => new Promise ( resolve => setTimeout ( resolve , time * 1000 ) ) ;
33+ const isString = < T > ( item : string | T ) : item is string => typeof item == 'string' ;
34+ const fileReferenceRegex = / \$ \{ f i l e \( ( .+ ) \) \} $ / ;
3335
3436export default class StepFunctionsOfflinePlugin implements Plugin {
3537 private location : string ;
@@ -203,6 +205,19 @@ export default class StepFunctionsOfflinePlugin implements Plugin {
203205 } ) ;
204206 }
205207
208+ private parseYaml < T > ( filename : string ) : Promise < T > {
209+ return this . serverless . yamlParser . parse ( filename ) ;
210+ }
211+
212+ private serverlessFileExists ( filename ) {
213+ const serverlessPath = this . serverless . config . servicePath ;
214+ if ( ! serverlessPath ) {
215+ throw new this . serverless . classes . Error ( 'Could not find serverless manifest' ) ;
216+ }
217+ const fullPath = path . join ( serverlessPath , filename ) ;
218+ return this . serverless . utils . fileExistsSync ( fullPath ) ;
219+ }
220+
206221 async getRawConfig ( ) : Promise < ServerlessWithError [ 'service' ] > {
207222 const serverlessPath = this . serverless . config . servicePath ;
208223 if ( ! serverlessPath ) {
@@ -211,27 +226,24 @@ export default class StepFunctionsOfflinePlugin implements Plugin {
211226
212227 const manifestFilenames = [ 'serverless.yaml' , 'serverless.yml' , 'serverless.json' , 'serverless.js' ] ;
213228
214- const manifestFilename = manifestFilenames
215- . map ( filename => path . join ( serverlessPath , filename ) )
216- . find ( filename => this . serverless . utils . fileExistsSync ( filename ) ) ;
217-
229+ const manifestFilename = manifestFilenames . find ( name => this . serverlessFileExists ( name ) ) ;
218230 if ( ! manifestFilename ) {
219231 throw new this . serverless . classes . Error (
220232 `Could not find serverless manifest at path ${ serverlessPath } . If this path is incorreect you should adjust the 'servicePath' variable`
221233 ) ;
222234 }
235+ const manifestPath = path . join ( serverlessPath , manifestFilename ) ;
223236 let fromFile : ServerlessWithError [ 'service' ] ;
224- if ( / \. j s o n | \. j s $ / . test ( manifestFilename ) ) {
237+ if ( / \. j s o n | \. j s $ / . test ( manifestPath ) ) {
225238 try {
226- fromFile = await import ( manifestFilename ) ;
239+ fromFile = await import ( manifestPath ) ;
227240 return fromFile ;
228241 } catch ( err ) {
229242 console . error ( err ) ;
230- throw new Error ( `Unable to import manifest at: ${ manifestFilename } ` ) ;
243+ throw new Error ( `Unable to import manifest at: ${ manifestPath } ` ) ;
231244 }
232245 }
233-
234- return this . serverless . yamlParser . parse ( manifestFilename ) ;
246+ return this . parseYaml < ServerlessWithError [ 'service' ] > ( manifestPath ) ;
235247 }
236248
237249 parseConfig ( ) : Promise < void > {
@@ -294,13 +306,35 @@ export default class StepFunctionsOfflinePlugin implements Plugin {
294306 return { handler : handlerName , filePath } ;
295307 }
296308
309+ async _loadStates ( states : StateMachine [ 'States' ] | string ) : Promise < StateMachine [ 'States' ] > {
310+ if ( isString ( states ) ) {
311+ const serverlessPath = this . serverless . config . servicePath ;
312+ if ( ! serverlessPath ) {
313+ throw new this . serverless . classes . Error ( 'Could not find serverless manifest' ) ;
314+ }
315+ const match = states . match ( fileReferenceRegex ) ;
316+ if ( ! match ) {
317+ throw new this . serverless . classes . Error ( `couldn't understand string of States: ${ states } ` ) ;
318+ }
319+ const fileName = match [ 1 ] ;
320+ if ( ! this . serverlessFileExists ( fileName ) ) {
321+ throw new this . serverless . classes . Error ( `Unable to find ${ fileName } in serverless path` ) ;
322+ }
323+ return this . parseYaml < StateMachine [ 'States' ] > ( path . join ( serverlessPath , fileName ) ) ;
324+ }
325+ return states ;
326+ }
327+
297328 async buildStepWorkFlow ( ) : Promise < ReturnType < StepFunctionsOfflinePlugin [ 'process' ] > > {
298329 this . cliLog ( 'Building StepWorkFlow' ) ;
299330 if ( ! this . stateDefinition ) throw new Error ( 'Missing state definition' ) ;
300331 const event = this . loadedEventFile ?? { } ;
301332 if ( ! this . stateDefinition ?. StartAt ) {
302333 throw new Error ( 'Missing `startAt` in definition' ) ;
303334 }
335+ if ( typeof this . stateDefinition . States === 'string' ) {
336+ this . stateDefinition . States = await this . _loadStates ( this . stateDefinition . States ) ;
337+ }
304338 this . addContextObject ( this . stateDefinition . States , this . stateDefinition . StartAt , event ) ;
305339 this . states = this . stateDefinition . States ;
306340 return this . process ( this . states [ this . stateDefinition . StartAt ] , this . stateDefinition . StartAt , event ) ;
@@ -311,6 +345,10 @@ export default class StepFunctionsOfflinePlugin implements Plugin {
311345 event : Event
312346 ) : Promise < ReturnType < StepFunctionsOfflinePlugin [ 'process' ] > > {
313347 this . cliLog ( 'Building Iterator StepWorkFlow' ) ;
348+
349+ if ( typeof stateDefinition . States === 'string' ) {
350+ stateDefinition . States = await this . _loadStates ( stateDefinition . States ) ;
351+ }
314352 this . addContextObject ( stateDefinition . States , stateDefinition . StartAt , event ) ;
315353
316354 if ( ! stateDefinition . States ) return ;
0 commit comments