Skip to content

Commit ac9c2e2

Browse files
committed
Initial commit
0 parents  commit ac9c2e2

8 files changed

+304
-0
lines changed

.editorconfig

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
insert_final_newline = true
6+
trim_trailing_whitespace = true
7+
8+
[{*.js,*.json,*.yml}]
9+
indent_size = 2
10+
indent_style = space

.eslintrc.yml

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
env:
2+
mocha: true
3+
node: true
4+
5+
extends:
6+
- airbnb-base
7+
- prettier
8+
9+
plugins:
10+
- prettier
11+
12+
rules:
13+
prettier/prettier:
14+
- error
15+
- singleQuote: true

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.DS_Store
2+
.nyc_output
3+
coverage
4+
node_modules

LICENSE.md

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2018 Kevin Rambaud
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

index.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require('./lib/base-path-plugin');

lib/base-path-plugin.js

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
const { join } = require('path');
2+
3+
/**
4+
* BasePathPlugin class.
5+
*/
6+
class BasePathPlugin {
7+
/**
8+
* Constructor.
9+
*
10+
* @param {Object} serverless Serverless
11+
* @param {Object} options Options
12+
*/
13+
constructor(serverless, options) {
14+
this.serverless = serverless;
15+
this.options = options;
16+
this.basePath =
17+
typeof this.serverless.service.custom.functionsBasePath === 'string'
18+
? this.serverless.service.custom.functionsBasePath
19+
: '';
20+
21+
this.hooks = {
22+
'before:run:run': this.rewriteHandlersPath.bind(this),
23+
'before:offline:start': this.rewriteHandlersPath.bind(this),
24+
'before:offline:start:init': this.rewriteHandlersPath.bind(this),
25+
'before:package:createDeploymentArtifacts': this.rewriteHandlersPath.bind(
26+
this
27+
),
28+
'before:deploy:function:packageFunction': this.rewriteHandlersPath.bind(
29+
this
30+
),
31+
'before:invoke:local:invoke': this.rewriteHandlersPath.bind(this)
32+
};
33+
}
34+
35+
/**
36+
* Rewrite handlers path of the instance's functions
37+
* based on the functionsBasePath custom property passed
38+
* through serverless config file.
39+
*
40+
* @returns {void}
41+
*/
42+
rewriteHandlersPath() {
43+
if (this.basePath) {
44+
const { functions } = this.serverless.service;
45+
46+
Object.keys(functions).forEach(functionName => {
47+
const handlerPath = functions[functionName].handler;
48+
49+
if (!handlerPath) {
50+
throw new Error(
51+
`handler path is not defined for function "${functionName}"`
52+
);
53+
}
54+
55+
if (typeof handlerPath !== 'string') {
56+
throw new Error(
57+
`handler path must be a string for function "${functionName}"`
58+
);
59+
}
60+
61+
functions[functionName].handler = join(this.basePath, handlerPath);
62+
});
63+
}
64+
}
65+
}
66+
67+
module.exports = BasePathPlugin;

lib/base-path-plugin.test.js

+140
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
const { join } = require('path');
2+
const { expect } = require('chai');
3+
4+
const BasePathPlugin = require('./base-path-plugin');
5+
6+
/**
7+
* Simple object copy to avoid reference copy.
8+
*
9+
* Note: only properties with primivate value will be copied,
10+
* properties with function assigned won't be copied.
11+
*
12+
* @param {Object} obj Object
13+
* @returns {Object} Copied Object
14+
*/
15+
function copyObj(obj) {
16+
return JSON.parse(JSON.stringify(obj));
17+
}
18+
19+
/**
20+
* Validate that the handler path rewriting match by comparing
21+
* the previous state of a set of functions and a new one.
22+
*
23+
* @param {string} basePath Bash path
24+
* @param {Object} previousFunctions Functions
25+
* @param {Object} currentFunctions Functions
26+
* @returns {void}
27+
*/
28+
function validatePath(basePath, prevFunctions, currFunctions) {
29+
Object.keys(prevFunctions).forEach(functionName => {
30+
const expectedPath = join(basePath, prevFunctions[functionName].handler);
31+
const newPath = currFunctions[functionName].handler;
32+
33+
expect(expectedPath).to.be.equal(newPath);
34+
});
35+
}
36+
37+
describe('BasePathPlugin', () => {
38+
beforeEach(() => {
39+
this.serverlessMock = {
40+
providers: {},
41+
service: {
42+
service: 'serverless-demo-functions-base-path',
43+
serviceObject: { name: 'serverless-demo-functions-base-path' },
44+
provider: {
45+
stage: 'dev',
46+
region: 'eu-west-1',
47+
variableSyntax: '',
48+
name: 'aws',
49+
runtime: 'nodejs6.10',
50+
memorySize: 128,
51+
timemout: 10,
52+
versionFunctions: true
53+
},
54+
custom: { functionsBasePath: 'src/handlers' },
55+
plugins: ['serverless-functions-base-path'],
56+
functions: {
57+
hello: {
58+
handler: 'users.hello',
59+
events: [{ http: { path: 'hello', method: 'get' } }],
60+
name: 'serverless-demo-functions-base-path-dev-hello'
61+
},
62+
bye: {
63+
handler: 'users.bye',
64+
events: [{ http: { path: 'bye', method: 'get' } }],
65+
name: 'serverless-demo-functions-base-path-dev-bye'
66+
}
67+
},
68+
resources: undefined,
69+
package: {}
70+
}
71+
};
72+
73+
this.optionsMock = {};
74+
});
75+
76+
it('should not rewrite the functions handler path if "basePath" is not specified', () => {
77+
this.serverlessMock.service.custom.functionsBasePath = null;
78+
79+
const Plugin = new BasePathPlugin(this.serverlessMock, this.optionsMock);
80+
const previousFunctions = copyObj(Plugin.serverless.service.functions);
81+
82+
Plugin.rewriteHandlersPath();
83+
84+
const currentFunctions = copyObj(Plugin.serverless.service.functions);
85+
86+
validatePath(Plugin.basePath, previousFunctions, currentFunctions);
87+
});
88+
89+
it('should not rewrite the functions handler path if "basePath" is something else than a string', () => {
90+
this.serverlessMock.service.custom.functionsBasePath = { prop: 'val' };
91+
92+
const Plugin = new BasePathPlugin(this.serverlessMock, this.optionsMock);
93+
const previousFunctions = copyObj(Plugin.serverless.service.functions);
94+
95+
Plugin.rewriteHandlersPath();
96+
97+
const currentFunctions = copyObj(Plugin.serverless.service.functions);
98+
99+
validatePath(Plugin.basePath, previousFunctions, currentFunctions);
100+
});
101+
102+
it('should rewrite the functions handler path if a "basePath" variable is passed', () => {
103+
const Plugin = new BasePathPlugin(this.serverlessMock, this.optionsMock);
104+
const previousFunctions = copyObj(Plugin.serverless.service.functions);
105+
106+
Plugin.rewriteHandlersPath();
107+
108+
const currentFunctions = copyObj(Plugin.serverless.service.functions);
109+
110+
validatePath(Plugin.basePath, previousFunctions, currentFunctions);
111+
});
112+
113+
it('should throw an error if handler path is not specified', () => {
114+
this.serverlessMock.service.functions.bye.handler = null;
115+
116+
const Plugin = new BasePathPlugin(this.serverlessMock, this.optionsMock);
117+
const testFunc = () => {
118+
Plugin.rewriteHandlersPath();
119+
};
120+
121+
expect(testFunc).to.throw(
122+
Error,
123+
/handler path is not defined for function/
124+
);
125+
});
126+
127+
it('should throw an error if handler path is something else than a string', () => {
128+
this.serverlessMock.service.functions.bye.handler = { props: 'val' };
129+
130+
const Plugin = new BasePathPlugin(this.serverlessMock, this.optionsMock);
131+
const testFunc = () => {
132+
Plugin.rewriteHandlersPath();
133+
};
134+
135+
expect(testFunc).to.throw(
136+
Error,
137+
/handler path must be a string for function/
138+
);
139+
});
140+
});

package.json

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{
2+
"name": "serverless-functions-base-path",
3+
"version": "1.0.0",
4+
"description": "Easily define a base path where your serverless functions are located",
5+
"main": "index.js",
6+
"directories": {
7+
"example": "example",
8+
"lib": "lib"
9+
},
10+
"scripts": {
11+
"coverage": "nyc report --reporter=text-lcov | codecov",
12+
"format": "prettier-eslint --write \"lib/**/*.js\"",
13+
"lint": "eslint --fix \"lib/**/*.js\"",
14+
"test": "nyc --reporter=html --reporter=text mocha lib/**/*.test.js"
15+
},
16+
"repository": {
17+
"type": "git",
18+
"url": "git+https://github.com/kevinrambaud/serverless-functions-base-path.git"
19+
},
20+
"keywords": [
21+
"serverless",
22+
"functions",
23+
"lambda",
24+
"path",
25+
"base",
26+
"path"
27+
],
28+
"author": "Kevin Rambaud <[email protected]> (https://twitter.com/kevinrambaud)",
29+
"license": "MIT",
30+
"bugs": {
31+
"url": "https://github.com/kevinrambaud/serverless-functions-base-path/issues"
32+
},
33+
"homepage": "https://github.com/kevinrambaud/serverless-functions-base-path#readme",
34+
"devDependencies": {
35+
"chai": "^4.1.2",
36+
"codecov": "^3.0.0",
37+
"eslint": "^4.16.0",
38+
"eslint-config-airbnb-base": "^12.1.0",
39+
"eslint-config-prettier": "^2.9.0",
40+
"eslint-plugin-import": "^2.8.0",
41+
"eslint-plugin-prettier": "^2.5.0",
42+
"mocha": "^5.0.0",
43+
"nyc": "^11.4.1",
44+
"prettier-eslint-cli": "^4.7.0"
45+
}
46+
}

0 commit comments

Comments
 (0)