Skip to content

Commit e7531dc

Browse files
kikarHyperBrain
authored andcommitted
Do not add .js extension to entries values (#167)
* Do not add .js extension to entries values * Find entries extension from filename * Better filesearch performance * Add warning for multiple files found * Do not match directories * Added unit tests. Throw error if handler is not found. * Fixed examples * Use preference sorting to find the most probable handler for multiple hits
1 parent ceaf43c commit e7531dc

File tree

5 files changed

+184
-37
lines changed

5 files changed

+184
-37
lines changed

examples/babel-dynamically-entries/webpack.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@ module.exports = {
1515
output: {
1616
libraryTarget: 'commonjs',
1717
path: path.join(__dirname, '.webpack'),
18-
filename: '[name]'
18+
filename: '[name].js'
1919
}
2020
};

examples/babel-multiple-statically-entries/webpack.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,6 @@ module.exports = {
1717
output: {
1818
libraryTarget: 'commonjs',
1919
path: path.join(__dirname, '.webpack'),
20-
filename: '[name]'
20+
filename: '[name].js'
2121
}
2222
};

lib/validate.js

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,63 @@
33
const BbPromise = require('bluebird');
44
const path = require('path');
55
const fse = require('fs-extra');
6+
const glob = require('glob');
67
const lib = require('./index');
78
const _ = require('lodash');
89

9-
function getEntryForFunction(serverlessFunction) {
10-
const handler = serverlessFunction.handler;
11-
const handlerFile = /(.*)\..*?$/.exec(handler)[1] + '.js';
12-
13-
// Create a valid entry key
14-
return {
15-
[handlerFile]: `./${handlerFile}`
16-
};
17-
};
10+
/**
11+
* For automatic entry detection we sort the found files to solve ambiguities.
12+
* This should cover most of the cases. For complex setups the user should
13+
* build his own entries with help of the other exports.
14+
*/
15+
const preferredExtensions = [
16+
'.js',
17+
'.ts',
18+
'.jsx'
19+
];
1820

1921
module.exports = {
2022
validate() {
23+
const getEntryExtension = fileName => {
24+
const files = glob.sync(`${fileName}.*`, {
25+
cwd: this.serverless.config.servicePath,
26+
nodir: true
27+
});
28+
29+
if (_.isEmpty(files)) {
30+
// If we cannot find any handler we should terminate with an error
31+
throw new this.serverless.classes.Error(`No matching handler found for '${fileName}'. Check your service definition.`);
32+
}
33+
34+
// Move preferred file extensions to the beginning
35+
const sortedFiles = _.uniq(
36+
_.concat(
37+
_.sortBy(
38+
_.filter(files, file => _.includes(preferredExtensions, path.extname(file))),
39+
a => _.size(a)
40+
),
41+
files
42+
)
43+
);
44+
45+
if (_.size(sortedFiles) > 1) {
46+
this.serverless.cli.log(`WARNING: More than one matching handlers found for '${fileName}'. Using '${_.first(sortedFiles)}'.`);
47+
}
48+
return path.extname(_.first(sortedFiles));
49+
}
50+
51+
const getEntryForFunction = serverlessFunction => {
52+
const handler = serverlessFunction.handler;
53+
const handlerFile = /(.*)\..*?$/.exec(handler)[1];
54+
55+
const ext = getEntryExtension(handlerFile);
56+
57+
// Create a valid entry key
58+
return {
59+
[handlerFile]: `./${handlerFile}${ext}`
60+
};
61+
};
62+
2163
this.webpackConfig = (
2264
this.serverless.service.custom &&
2365
this.serverless.service.custom.webpack ||
@@ -38,7 +80,7 @@ module.exports = {
3880
});
3981
}
4082
lib.entries = entries;
41-
83+
4284
// Expose service file and options
4385
lib.serverless = this.serverless;
4486
lib.options = this.options;

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"body-parser": "^1.15.2",
3232
"express": "^4.14.0",
3333
"fs-extra": "^0.26.7",
34+
"glob": "^7.1.2",
3435
"lodash": "^4.17.4",
3536
"npm-programmatic": "0.0.5",
3637
"ts-node": "^3.2.0"

tests/validate.test.js

Lines changed: 129 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,24 @@ const makeFsExtraMock = require('./fs-extra.mock');
1010
chai.use(require('sinon-chai'));
1111
const expect = chai.expect;
1212

13+
const globMock = {
14+
sync() {}
15+
};
16+
1317
describe('validate', () => {
1418
let fsExtraMock;
1519
let baseModule;
1620
let module;
1721
let serverless;
22+
let sandbox;
1823

1924
before(() => {
25+
sandbox = sinon.sandbox.create();
26+
2027
mockery.enable({ warnOnUnregistered: false });
2128
fsExtraMock = makeFsExtraMock();
2229
mockery.registerMock('fs-extra', fsExtraMock);
30+
mockery.registerMock('glob', globMock);
2331
baseModule = require('../lib/validate');
2432
Object.freeze(baseModule);
2533
});
@@ -31,13 +39,20 @@ describe('validate', () => {
3139

3240
beforeEach(() => {
3341
serverless = new Serverless();
42+
serverless.cli = {
43+
log: sandbox.stub()
44+
};
3445
fsExtraMock._resetSpies();
3546
module = Object.assign({
3647
serverless,
3748
options: {},
3849
}, baseModule);
3950
});
4051

52+
afterEach(() => {
53+
sandbox.restore();
54+
});
55+
4156
it('should expose a `validate` method', () => {
4257
expect(module.validate).to.be.a('function');
4358
});
@@ -266,6 +281,12 @@ describe('validate', () => {
266281
});
267282

268283
describe('entries', () => {
284+
let globSyncStub;
285+
286+
beforeEach(() => {
287+
globSyncStub = sandbox.stub(globMock, 'sync');
288+
});
289+
269290
const testFunctionsConfig = {
270291
func1: {
271292
handler: 'module1.func1handler',
@@ -305,7 +326,7 @@ describe('validate', () => {
305326
},
306327
};
307328

308-
it('should expose entries from serverless.yml if `options.function` is not defined', () => {
329+
it('should expose all functions if `options.function` is not defined', () => {
309330
const testOutPath = 'test';
310331
const testConfig = {
311332
entry: 'test',
@@ -316,22 +337,24 @@ describe('validate', () => {
316337
};
317338
module.serverless.service.custom.webpack = testConfig;
318339
module.serverless.service.functions = testFunctionsConfig;
319-
return module
320-
.validate()
321-
.then(() => {
322-
const lib = require('../lib/index');
323-
const expectedLibEntries = {
324-
'module1.js': './module1.js',
325-
'module2.js': './module2.js',
326-
'handlers/func3/module2.js': './handlers/func3/module2.js',
327-
'handlers/module2/func3/module2.js': './handlers/module2/func3/module2.js',
328-
};
329-
330-
expect(lib.entries).to.deep.eq(expectedLibEntries)
331-
});
340+
globSyncStub.callsFake(filename => [ _.replace(filename, '*', 'js') ]);
341+
return expect(module.validate()).to.be.fulfilled
342+
.then(() => {
343+
const lib = require('../lib/index');
344+
const expectedLibEntries = {
345+
'module1': './module1.js',
346+
'module2': './module2.js',
347+
'handlers/func3/module2': './handlers/func3/module2.js',
348+
'handlers/module2/func3/module2': './handlers/module2/func3/module2.js',
349+
};
350+
351+
expect(lib.entries).to.deep.equal(expectedLibEntries);
352+
expect(globSyncStub).to.have.callCount(4);
353+
expect(serverless.cli.log).to.not.have.been.called;
354+
});
332355
});
333356

334-
it('should expose entries with `options.function` value if `options.function` is defined and found in entries from serverless.yml', () => {
357+
it('should expose the requested function if `options.function` is defined and the function is found', () => {
335358
const testOutPath = 'test';
336359
const testFunction = 'func1';
337360
const testConfig = {
@@ -344,16 +367,97 @@ describe('validate', () => {
344367
module.serverless.service.custom.webpack = testConfig;
345368
module.serverless.service.functions = testFunctionsConfig;
346369
module.options.function = testFunction;
347-
return module
348-
.validate()
349-
.then(() => {
350-
const lib = require('../lib/index');
351-
const expectedLibEntries = {
352-
'module1.js': './module1.js'
353-
};
354-
355-
expect(lib.entries).to.deep.eq(expectedLibEntries)
356-
});
370+
globSyncStub.callsFake(filename => [ _.replace(filename, '*', 'js') ]);
371+
return expect(module.validate()).to.be.fulfilled
372+
.then(() => {
373+
const lib = require('../lib/index');
374+
const expectedLibEntries = {
375+
'module1': './module1.js'
376+
};
377+
378+
expect(lib.entries).to.deep.equal(expectedLibEntries)
379+
expect(globSyncStub).to.have.been.calledOnce;
380+
expect(serverless.cli.log).to.not.have.been.called;
381+
});
382+
});
383+
384+
it('should show a warning if more than one matching handler is found', () => {
385+
const testOutPath = 'test';
386+
const testFunction = 'func1';
387+
const testConfig = {
388+
entry: 'test',
389+
context: 'testcontext',
390+
output: {
391+
path: testOutPath,
392+
},
393+
};
394+
module.serverless.service.custom.webpack = testConfig;
395+
module.serverless.service.functions = testFunctionsConfig;
396+
module.options.function = testFunction;
397+
globSyncStub.returns([ 'module1.ts', 'module1.js' ]);
398+
return expect(module.validate()).to.be.fulfilled
399+
.then(() => {
400+
const lib = require('../lib/index');
401+
const expectedLibEntries = {
402+
'module1': './module1.ts'
403+
};
404+
405+
expect(lib.entries).to.deep.equal(expectedLibEntries)
406+
expect(globSyncStub).to.have.been.calledOnce;
407+
expect(serverless.cli.log).to.have.been.calledOnce;
408+
expect(serverless.cli.log).to.have.been.calledWith(
409+
'WARNING: More than one matching handlers found for \'module1\'. Using \'module1.ts\'.'
410+
);
411+
});
412+
});
413+
414+
it('should select the most probable handler if multiple hits are found', () => {
415+
const testOutPath = 'test';
416+
const testFunction = 'func1';
417+
const testConfig = {
418+
entry: 'test',
419+
context: 'testcontext',
420+
output: {
421+
path: testOutPath,
422+
},
423+
};
424+
module.serverless.service.custom.webpack = testConfig;
425+
module.serverless.service.functions = testFunctionsConfig;
426+
module.options.function = testFunction;
427+
globSyncStub.returns([ 'module1.doc', 'module1.json', 'module1.test.js', 'module1.ts', 'module1.js' ]);
428+
return expect(module.validate()).to.be.fulfilled
429+
.then(() => {
430+
const lib = require('../lib/index');
431+
const expectedLibEntries = {
432+
'module1': './module1.ts'
433+
};
434+
435+
expect(lib.entries).to.deep.equal(expectedLibEntries)
436+
expect(globSyncStub).to.have.been.calledOnce;
437+
expect(serverless.cli.log).to.have.been.calledOnce;
438+
expect(serverless.cli.log).to.have.been.calledWith(
439+
'WARNING: More than one matching handlers found for \'module1\'. Using \'module1.ts\'.'
440+
);
441+
});
442+
});
443+
444+
it('should throw an exception if no handler is found', () => {
445+
const testOutPath = 'test';
446+
const testFunction = 'func1';
447+
const testConfig = {
448+
entry: 'test',
449+
context: 'testcontext',
450+
output: {
451+
path: testOutPath,
452+
},
453+
};
454+
module.serverless.service.custom.webpack = testConfig;
455+
module.serverless.service.functions = testFunctionsConfig;
456+
module.options.function = testFunction;
457+
globSyncStub.returns([]);
458+
expect(() => {
459+
module.validate();
460+
}).to.throw(/No matching handler found for/);
357461
});
358462

359463
it('should throw an exception if `options.function` is defined but not found in entries from serverless.yml', () => {

0 commit comments

Comments
 (0)