Skip to content

Commit 63d58b5

Browse files
authored
Merge pull request #10 from vkkis93/feat/v2
Feat/v2
2 parents 67392f9 + 14801fa commit 63d58b5

File tree

17 files changed

+3848
-241
lines changed

17 files changed

+3848
-241
lines changed

.codeclimate.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
version: "2"
22
checks:
3+
file-lines:
4+
config:
5+
threshold: 500
36
method-complexity:
47
config:
58
threshold: 50
69
method-lines:
710
config:
811
threshold: 100
12+
return-statements:
13+
enabled: true
14+
config:
15+
threshold: 10
916
plugins:
1017
eslint:
1118
enabled: true

.eslintignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
node_modules
2+
tests
23
*.log
34
*.json
45
*.lock
56
*.md
6-
LICENSE
7+
LICENSE

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
logs
33
*.log
44
npm-debug.log
5-
5+
.nyc_output
66
*.zip
77
dist
88

README.md

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
[![Known Vulnerabilities](https://snyk.io/test/github/vkkis93/serverless-step-functions-offline/badge.svg?targetFile=package.json)](https://snyk.io/test/github/vkkis93/serverless-step-functions-offline?targetFile=package.json)
44
[![Maintainability](https://api.codeclimate.com/v1/badges/b321644ef368976aee12/maintainability)](https://codeclimate.com/github/vkkis93/serverless-step-functions-offline/maintainability)
55
[![Dependency Status](https://david-dm.org/vkkis93/serverless-step-functions-offline.svg)](https://david-dm.org/vkkis93/serverless-step-functions-offline)
6+
[![NPM](https://nodei.co/npm/serverless-step-functions-offline.png)](https://nodei.co/npm/serverless-step-functions-offline/)
67

78
# serverless-step-functions-offline
8-
9+
:warning: Version 2.0 with breaking changes see [usage](#usage) :warning:
910
## Documentation
1011

1112
- [Install](#install)
@@ -51,7 +52,7 @@ You must have this plugin installed and correctly specified statemachine definit
5152
Example of statemachine definition you can see [here](https://github.com/horike37/serverless-step-functions#setup).
5253

5354
# Usage
54-
After all steps are done, need to add to section **custom** in serverless.yml the key **stepFunctionsOffline** with properties *stateName*: path to lambda function.
55+
After all steps are done, need to add to section **custom** in serverless.yml the key **stepFunctionsOffline** with properties *stateName*: name of lambda function.
5556

5657
For example:
5758

@@ -65,11 +66,18 @@ plugins:
6566

6667
custom:
6768
stepFunctionsOffline:
68-
FirstLambda: firstLambda/index.handler
69+
stepOne: firstLambda (v2.0)
6970
# ...
7071
# ...
71-
SecondLambda: myDir/index.main
72-
72+
stepTwo: secondLambda (v2.0)
73+
74+
functions:
75+
firstLambda:
76+
handler: firstLambda/index.handler
77+
name: TheFirstLambda
78+
secondLambda:
79+
handler: secondLambda/index.handler
80+
name: TheSecondLambda
7381
stepFunctions:
7482
stateMachines:
7583
foo:
@@ -88,8 +96,8 @@ stepFunctions:
8896
```
8997
9098
Where:
91-
- `FirstLambda` is the name of step in state machine
92-
- `firstLambda/index.handler` is the path to Lambda
99+
- `StepOne` is the name of step in state machine
100+
- `firstLambda` is the name of function in section **functions**
93101

94102
# Run Plugin
95103
```bash
@@ -106,23 +114,24 @@ By default `process.env.STEP_IS_OFFLINE = true`.
106114
# What does plugin support?
107115
| States | Support |
108116
| ------ | ------ |
109-
| ***Task*** | At this moment plugin **does not support fields** *Retry*, *Catch*, *TimeoutSeconds*, *HeartbeatSeconds*
117+
| ***Task*** | At this moment plugin **does not support fields** *Retry*, *Catch*, *TimeoutSeconds*, *HeartbeatSeconds* |
118+
| ***Choice*** | All comparison operators except: *And*, *Not*, *Or* |
110119
| ***Wait*** | All following fields: *Seconds*, *SecondsPath*, *Timestamp*, *TimestampPath* |
111-
| ***Choice*** | All comparison operators except: *And*, *Not*, *Or*|
112-
| ***Pass*** | * |
113-
| ***Parallel*** | Only *Branches*
120+
| ***Parallel*** | Only *Branches* |
121+
| ***Pass*** | Result, ResultPath |
122+
| ***Fail***| Cause, Error|
123+
| ***Succeed***| |
124+
125+
114126

115127
### TODOs
116128
- [x] Support context object
117129
- [x] Improve performance
118130
- [x] Fixing bugs
119-
- [ ] Add unit tests - to make plugin stable (next step)
120-
- [ ] Support fields *Retry*, *Catch*
131+
- [x] Support Pass, Fail, Succeed
132+
- [ ] Add unit tests - to make plugin stable (next step) - [ ] Support fields *Retry*, *Catch*
121133
- [ ] Support other languages except node.js
122134

123-
124-
If you have any questions, feel free to contact me: [email protected]
125-
126135
# License
127136

128137
MIT

build.js

Lines changed: 113 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,16 @@ const enumList = require('./enum');
77

88
module.exports = {
99

10-
findFunctionsPathAndHandler() {
11-
for (const functionName in this.variables) {
12-
const functionHandler = this.variables[functionName];
13-
const {handler, filePath} = this._findFunctionPathAndHandler(functionHandler);
14-
15-
this.variables[functionName] = {handler, filePath};
16-
}
17-
},
18-
10+
// findFunctionsPathAndHandler() {
11+
// for (const functionName in this.variables) {
12+
// const functionHandler = this.variables[functionName];
13+
// const {handler, filePath} = this._findFunctionPathAndHandler(functionHandler);
14+
//
15+
// this.variables[functionName] = {handler, filePath};
16+
// }
17+
// console.log('this.va', this.variables)
18+
// },
19+
//
1920
_findFunctionPathAndHandler(functionHandler) {
2021
const dir = path.dirname(functionHandler);
2122
const handler = path.basename(functionHandler);
@@ -33,11 +34,10 @@ module.exports = {
3334

3435
return Promise.resolve()
3536
.then(() => this.process(this.states[this.stateDefinition.StartAt], this.stateDefinition.StartAt, this.eventFile))
36-
.then(() => this.cliLog('Serverless step function offline: Finished'))
3737
.catch(err => {
38-
console.log('OOPS', err.stack);
39-
this.cliLog(err);
40-
process.exit(1);
38+
// console.log('OOPS', err.stack);
39+
// this.cliLog(err);
40+
throw err;
4141
});
4242
},
4343

@@ -46,7 +46,13 @@ module.exports = {
4646
this.eventForParallelExecution = event;
4747
}
4848
const data = this._findStep(state, stateName);
49-
if (!data || data instanceof Promise) {return data;}
49+
// if (data instanceof Promise) return Promise.resolve();
50+
if (!data || data instanceof Promise) {
51+
if (!state || state.Type !== 'Parallel') {
52+
this.cliLog('Serverless step function offline: Finished');
53+
}
54+
return Promise.resolve();
55+
}
5056
if (data.choice) {
5157
return this._runChoice(data, event);
5258
} else {
@@ -56,27 +62,44 @@ module.exports = {
5662

5763
_findStep(currentState, currentStateName) {
5864
// it means end of states
59-
if (!currentState) {return;}
65+
if (!currentState) {
66+
this.currentState = null;
67+
this.currentStateName = null;
68+
return;
69+
}
6070
this.currentState = currentState;
61-
return this._switcherByType(currentState, currentStateName);
71+
this.currentStateName = currentStateName;
72+
return this._states(currentState, currentStateName);
6273
},
6374

64-
6575
_run(f, event) {
66-
return new Promise((resolve, reject) => {
67-
if (!f) return Promise.resolve();// end of states
68-
f(event, this.contextObject, this.contextObject.done);
69-
}).catch(err => {
70-
throw err;
71-
});
76+
if (!f) {
77+
return;
78+
}// end of states
79+
this.executionLog(`~~~~~~~~~~~~~~~~~~~~~~~~~~~ ${this.currentStateName} started ~~~~~~~~~~~~~~~~~~~~~~~~~~~`);
80+
f(event, this.contextObject, this.contextObject.done);
81+
7282
},
7383

74-
_switcherByType(currentState, currentStateName) {
84+
_states(currentState, currentStateName) {
7585
switch (currentState.Type) {
7686
case 'Task': // just push task to general array
87+
//before each task restore global default env variables
88+
process.env = Object.assign({}, this.environmentVariables);
89+
let f = this.variables[currentStateName];
90+
f = this.functions[f];
91+
if (!f) {
92+
this.cliLog(`Function "${currentStateName}" does not presented in serverless.yml`);
93+
process.exit(1);
94+
}
95+
const {handler, filePath} = this._findFunctionPathAndHandler(f.handler);
96+
// if function has additional variables - attach it to function
97+
if (f.environment) {
98+
process.env = _.extend(process.env, f.environment);
99+
}
77100
return {
78101
name: currentStateName,
79-
f: () => require(path.join(process.cwd(), this.variables[currentStateName].filePath))[this.variables[currentStateName].handler]
102+
f: () => require(path.join(this.location, filePath))[handler]
80103
};
81104
case 'Parallel': // look through branches and push all of them
82105
this.eventParallelResult = [];
@@ -137,11 +160,50 @@ module.exports = {
137160
}
138161
};
139162
case 'Pass':
140-
return {f: () => this.cliLog('PASS STATE')};
163+
return {
164+
f: (event) => {
165+
return (arg1, arg2, cb) => {
166+
this.cliLog('!!! Pass State !!!');
167+
const eventResult = this._passStateFields(currentState, event);
168+
cb(null, eventResult);
169+
170+
};
171+
}
172+
};
173+
174+
case 'Succeed':
175+
this.cliLog('Succeed');
176+
return Promise.resolve('Succeed');
177+
case 'Fail':
178+
const obj = {};
179+
if (currentState.Cause) obj.Cause = currentState.Cause;
180+
if (currentState.Error) obj.Error = currentState.Error;
181+
this.cliLog('Fail');
182+
if (!_.isEmpty(obj)) {
183+
this.cliLog(JSON.stringify(obj));
184+
}
185+
return Promise.resolve('Fail');
141186
}
142187
return;
143188
},
144189

190+
_passStateFields(currentState, event) {
191+
if (!currentState.ResultPath) {
192+
if (!currentState.Result) {
193+
return event;
194+
}
195+
return currentState.Result;
196+
} else {
197+
const variableName = currentState.ResultPath.split('$.')[1];
198+
if (!currentState.Result) {
199+
event[variableName] = event;
200+
return event;
201+
}
202+
event[variableName] = currentState.Result;
203+
return event;
204+
}
205+
},
206+
145207
_runChoice(data, result) {
146208
let existsAnyMatches = false;
147209

@@ -170,8 +232,8 @@ module.exports = {
170232
const waitField = _.omit(currentState, 'Type', 'Next', 'Result');
171233
const waitKey = Object.keys(waitField)[0];
172234
if (!waitListKeys.includes(waitKey)) {
173-
this.cliLog(`Plugin does not support wait operator "${waitKey}"`);
174-
process.exit();
235+
const error = `Plugin does not support wait operator "${waitKey}"`;
236+
throw new this.serverless.classes.Error(error);
175237
}
176238
switch (Object.keys(waitField)[0]) {
177239
case 'Seconds':
@@ -185,11 +247,10 @@ module.exports = {
185247
case 'TimestampPath':
186248
const timestampPath = waitField['TimestampPath'].split('$.')[1];
187249
if (!event[timestampPath]) {
188-
this.cliLog(
250+
const error =
189251
`An error occurred while executing the state ${currentStateName}.
190-
The TimestampPath parameter does not reference an input value: ${waitField['TimestampPath']}`
191-
);
192-
process.exit(1);
252+
The TimestampPath parameter does not reference an input value: ${waitField['TimestampPath']}`;
253+
throw new this.serverless.classes.Error(error);
193254
}
194255
targetTime = moment(event[timestampPath]);
195256
timeDiff = targetTime.diff(currentTime, 'seconds');
@@ -199,11 +260,10 @@ module.exports = {
199260
const secondsPath = waitField['SecondsPath'].split('$.')[1];
200261
const waitSeconds = event[secondsPath];
201262
if (!waitSeconds) {
202-
this.cliLog(`
263+
const error = `
203264
An error occurred while executing the state ${currentStateName}.
204-
The TimestampPath parameter does not reference an input value: ${waitField['SecondsPath']}`
205-
);
206-
process.exit(1);
265+
The TimestampPath parameter does not reference an input value: ${waitField['SecondsPath']}`;
266+
throw new this.serverless.classes.Error(error);
207267
}
208268
waitTimer = waitSeconds;
209269
break;
@@ -213,17 +273,19 @@ module.exports = {
213273

214274
createContextObject() {
215275
const cb = (err, result) => {
216-
return new Promise((resolve, reject) => {
217-
if (err) {
218-
throw `Error in function "${this.currentState.name}": ${JSON.stringify(err)}`; //;TODO NAME
219-
}
220-
let state = this.states;
221-
if (this.parallelBranch && this.parallelBranch.States) {
222-
state = this.parallelBranch.States;
223-
if (!this.currentState.Next) this.eventParallelResult.push(result); //it means the end of execution of branch
224-
}
225-
this.process(state[this.currentState.Next], this.currentState.Next, result);
226-
});
276+
// return new Promise((resolve, reject) => {
277+
if (err) {
278+
throw `Error in function "${this.currentStateName}": ${JSON.stringify(err)}`;
279+
}
280+
this.executionLog(`~~~~~~~~~~~~~~~~~~~~~~~~~~~ ${this.currentStateName} finished ~~~~~~~~~~~~~~~~~~~~~~~~~~~`);
281+
let state = this.states;
282+
if (this.parallelBranch && this.parallelBranch.States) {
283+
state = this.parallelBranch.States;
284+
if (!this.currentState.Next) this.eventParallelResult.push(result); //it means the end of execution of branch
285+
}
286+
this.process(state[this.currentState.Next], this.currentState.Next, result);
287+
// return resolve();
288+
// });
227289
};
228290

229291
return {
@@ -233,5 +295,9 @@ module.exports = {
233295
fail: (err) => cb(err)
234296
};
235297

298+
},
299+
300+
executionLog(log) {
301+
if (this.detailedLog) this.cliLog(log);
236302
}
237303
};

0 commit comments

Comments
 (0)