Skip to content

Commit 15255a0

Browse files
author
Alice
authored
Merge pull request #13 from beforeyoubid/issue/12-support-file-references
Issue/12 support file references
2 parents d460d4a + f160a00 commit 15255a0

File tree

5 files changed

+75
-36
lines changed

5 files changed

+75
-36
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@beforeyoubid/serverless-step-functions-offline",
3-
"version": "2.4.2",
3+
"version": "2.5.0",
44
"description": "Serverlesss plugin to support step function offline",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",

src/__tests__/functions.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
firstLambda:
2+
handler: examples/firstLambda/index.handler
3+
name: TheFirstLambda

src/__tests__/serverless.yml

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -20,36 +20,12 @@ custom:
2020
FirstLambda: firstLambda
2121

2222
functions:
23-
firstLambda:
24-
handler: examples/firstLambda/index.handler
25-
name: TheFirstLambda
23+
- ${file(functions.yml)}
2624

2725
stepFunctions:
2826
stateMachines:
2927
foo:
3028
definition:
3129
Comment: "An example of the Amazon States Language using wait states"
3230
StartAt: FirstLambda
33-
States:
34-
FirstLambda:
35-
Type: Task
36-
Next: wait_using_seconds
37-
wait_using_seconds:
38-
Type: Wait
39-
Seconds: 2
40-
Next: wait_using_timestamp
41-
wait_using_timestamp:
42-
Type: Wait
43-
Timestamp: '2015-09-04T01:59:00Z'
44-
Next: wait_using_timestamp_path
45-
wait_using_timestamp_path:
46-
Type: Wait
47-
TimestampPath: "$.expirydate"
48-
Next: wait_using_seconds_path
49-
wait_using_seconds_path:
50-
Type: Wait
51-
SecondsPath: "$.expiryseconds"
52-
Next: FinalState
53-
FinalState:
54-
Type: Pass
55-
End: true
31+
States: ${file(states.yml)}

src/__tests__/states.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
FirstLambda:
2+
Type: Task
3+
Next: wait_using_seconds
4+
wait_using_seconds:
5+
Type: Wait
6+
Seconds: 2
7+
Next: wait_using_timestamp
8+
wait_using_timestamp:
9+
Type: Wait
10+
Timestamp: '2015-09-04T01:59:00Z'
11+
Next: wait_using_timestamp_path
12+
wait_using_timestamp_path:
13+
Type: Wait
14+
TimestampPath: "$.expirydate"
15+
Next: wait_using_seconds_path
16+
wait_using_seconds_path:
17+
Type: Wait
18+
SecondsPath: "$.expiryseconds"
19+
Next: FinalState
20+
FinalState:
21+
Type: Pass
22+
End: true

src/index.ts

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ import {
3030
import enumList from './enum';
3131

3232
const 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 = /\$\{file\((.+)\)\}$/;
3335

3436
export 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 (/\.json|\.js$/.test(manifestFilename)) {
237+
if (/\.json|\.js$/.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

Comments
 (0)