serverless-offline
Advanced tools
Comparing version 2.2.7 to 2.2.8
{ | ||
"name": "serverless-offline", | ||
"version": "2.2.7", | ||
"version": "2.2.8", | ||
"description": "Emulate AWS λ and API Gateway locally when developing your Serverless project", | ||
@@ -48,8 +48,8 @@ "main": "src/index.js", | ||
"coffee-script": "^1.10.0", | ||
"hapi": "^13.2.2", | ||
"hapi": "^13.3.0", | ||
"js-string-escape": "^1.0.1", | ||
"jsonpath-plus": "^0.15.0", | ||
"lodash.isplainobject": "^4.0.3", | ||
"lodash.isplainobject": "^4.0.4", | ||
"velocityjs": "^0.7.5" | ||
} | ||
} |
'use strict'; | ||
/* | ||
Mimicks the lambda context object | ||
http://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-context.html | ||
Mimicks the lambda context object | ||
http://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-context.html | ||
*/ | ||
@@ -7,0 +7,0 @@ module.exports = function createLambdaContext(fun, cb) { |
'use strict'; | ||
const jsonPath = require('./jsonPath'); | ||
const escapeJavaScript = require('js-string-escape'); | ||
// const escapeJavaScript = require('js-string-escape'); | ||
const escapeJavaScript = require('jsesc'); | ||
@@ -6,0 +7,0 @@ /* |
120
src/index.js
'use strict'; | ||
/* | ||
I'm against monolithic code like this file but splitting it induces unneeded complexity | ||
*/ | ||
module.exports = S => { | ||
// One-line coffee-script support | ||
require('coffee-script/register'); | ||
// Node dependencies | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
// External dependencies | ||
const Hapi = require('hapi'); | ||
const isPlainObject = require('lodash.isplainobject'); | ||
const debugLog = require('./debugLog'); | ||
const serverlessLog = S.config && S.config.serverlessPath ? | ||
@@ -17,2 +22,4 @@ require(path.join(S.config.serverlessPath, 'utils', 'cli')).log : | ||
// Internal lib | ||
const debugLog = require('./debugLog'); | ||
const jsonPath = require('./jsonPath'); | ||
@@ -36,6 +43,6 @@ const createLambdaContext = require('./createLambdaContext'); | ||
S.addAction(this.start.bind(this), { | ||
handler: 'start', | ||
context: 'offline', // calling 'sls offline' | ||
contextAction: 'start', // followed by 'start' | ||
handler: 'start', // will invoke the start method | ||
description: 'Simulates API Gateway to call your lambda functions offline', | ||
context: 'offline', | ||
contextAction: 'start', | ||
options: [ | ||
@@ -81,5 +88,6 @@ { | ||
// Entry point for the plugin (sls offline start) | ||
start(optionsAndData) { | ||
// this._logAndExit(optionsAndData); | ||
// Serverless version checking | ||
const version = S._version; | ||
@@ -91,12 +99,15 @@ if (!version.startsWith('0.5')) { | ||
process.env.IS_OFFLINE = true; | ||
this.envVars = {}; | ||
this.project = S.getProject(); | ||
this.requests = {}; // Will store the state of each request | ||
// Internals | ||
process.env.IS_OFFLINE = true; // Some users would like to know their environment outside of the handler | ||
this.project = S.getProject(); // All the project data | ||
this.requests = {}; // Maps a request id to the request's state (done: bool, timeout: timer) | ||
this.envVars = {}; // Env vars are specific to each handler | ||
this._setOptions(); | ||
this._registerBabel(); | ||
this._createServer(); | ||
this._createRoutes(); | ||
this._listen(); | ||
// Methods | ||
this._setOptions(); // Will create meaningful options from cli options | ||
this._registerBabel(); // Support for ES6 | ||
this._createServer(); // Hapijs boot | ||
this._createRoutes(); // API Gateway emulation | ||
this._create404Route(); // Not found handling | ||
this._listen(); // Hapijs listen | ||
} | ||
@@ -117,2 +128,3 @@ | ||
// Applies defaults | ||
this.options = { | ||
@@ -122,4 +134,4 @@ port: userOptions.port || 3000, | ||
stage: userOptions.stage || stagesKeys[0], | ||
httpsProtocol: userOptions.httpsProtocol || '', | ||
skipCacheInvalidation: userOptions.skipCacheInvalidation || false, | ||
httpsProtocol: userOptions.httpsProtocol || '', | ||
}; | ||
@@ -148,2 +160,3 @@ | ||
// Babel options can vary from handler to handler just like env vars | ||
const options = isBabelRuntime ? | ||
@@ -156,2 +169,3 @@ babelRuntimeOptions || { presets: ['es2015'] } : | ||
// We invoke babel-register only once | ||
if (!this.babelRegister) { | ||
@@ -162,2 +176,3 @@ debugLog('For the first time'); | ||
// But re-set the options at each handler invocation | ||
this.babelRegister(options); | ||
@@ -169,2 +184,3 @@ } | ||
// Hapijs server creation | ||
this.server = new Hapi.Server({ | ||
@@ -181,2 +197,3 @@ connections: { | ||
// HTTPS support | ||
if (typeof httpsDir === 'string' && httpsDir.length > 0) connectionOptions.tls = { | ||
@@ -187,2 +204,3 @@ key: fs.readFileSync(path.resolve(httpsDir, 'key.pem'), 'ascii'), | ||
// Passes the configuration object to the server | ||
this.server.connection(connectionOptions); | ||
@@ -198,5 +216,9 @@ } | ||
// Runtime checks | ||
// No python :'( | ||
// No python or Java :'( | ||
const funRuntime = fun.runtime; | ||
if (funRuntime !== 'nodejs' && funRuntime !== 'babel') return; | ||
if (['nodejs', 'nodejs4.3', 'babel'].indexOf(funRuntime) === -1) { | ||
console.log(); | ||
serverlessLog(`Warning: found unsupported runtime '${funRuntime}' for function '${fun.name}'`); | ||
return; | ||
} | ||
@@ -226,3 +248,3 @@ // Templates population (with project variables) | ||
// Add a route for each endpoint | ||
// Adds a route for each endpoint | ||
populatedFun.endpoints.forEach(endpoint => { | ||
@@ -251,3 +273,3 @@ | ||
config, | ||
handler: (request, reply) => { | ||
handler: (request, reply) => { // Here we go | ||
console.log(); | ||
@@ -260,3 +282,3 @@ serverlessLog(`${method} ${request.url.path} (λ: ${funName})`); | ||
// Shared mutable state is the root of all evil | ||
// Shared mutable state is the root of all evil they say | ||
const requestId = Math.random().toString().slice(2); | ||
@@ -276,5 +298,5 @@ this.requests[requestId] = { done: false }; | ||
/* ENVIRONMENT VARIABLES CONFIGURATION */ | ||
/* ENVIRONMENT VARIABLES DECLARATION */ | ||
// Clear old vars | ||
// Clears old vars | ||
for (let key in this.envVars) { | ||
@@ -284,3 +306,3 @@ delete process.env[key]; | ||
// Declare new ones | ||
// Declares new ones | ||
this.envVars = isPlainObject(populatedFun.environment) ? populatedFun.environment : {}; | ||
@@ -297,3 +319,4 @@ for (let key in this.envVars) { | ||
let handler; | ||
let handler; // The lambda function | ||
try { | ||
@@ -304,3 +327,4 @@ if (!this.options.skipCacheInvalidation) { | ||
for (let key in require.cache) { | ||
// Require cache invalidation, brutal and fragile. Might cause errors, if so, please submit issue. | ||
// Require cache invalidation, brutal and fragile. | ||
// Might cause errors, if so please submit an issue. | ||
if (!key.match('node_modules')) delete require.cache[key]; | ||
@@ -312,3 +336,3 @@ } | ||
handler = require(handlerPath)[handlerParts[1]]; | ||
if (typeof handler !== 'function') throw new Error(`Serverless-offline: handler for function ${funName} is not a function`); | ||
if (typeof handler !== 'function') throw new Error(`Serverless-offline: handler for '${funName}' is not a function`); | ||
} | ||
@@ -319,9 +343,10 @@ catch(err) { | ||
/* REQUEST TEMPLATE PROCESSING (event population) */ | ||
let event = {}; | ||
/* REQUEST TEMPLATE PROCESSING (event population) */ | ||
if (requestTemplate) { | ||
try { | ||
debugLog('_____ REQUEST TEMPLATE PROCESSING _____'); | ||
// Velocity templating language parsing | ||
const velocityContext = createVelocityContext(request, this.velocityContextOptions, request.payload || {}); | ||
@@ -340,11 +365,12 @@ event = renderVelocityTemplateObject(requestTemplate, velocityContext); | ||
const lambdaContext = createLambdaContext(fun, (err, data) => { | ||
// Everything in this block happens once the lambda function has resolved | ||
debugLog('_____ HANDLER RESOLVED _____'); | ||
// Timeout resolving | ||
// Timeout clearing if needed | ||
if (this._clearTimeout(requestId)) return; | ||
// User sould not call context.done twice | ||
// User should not call context.done twice | ||
if (this.requests[requestId].done) { | ||
console.log(); | ||
serverlessLog('Warning: context.done called twice!'); | ||
serverlessLog(`Warning: context.done called twice within handler '${funName}'!`); | ||
debugLog('requestId:', requestId); | ||
@@ -365,4 +391,4 @@ return; | ||
const errorMessage = err.message ? err.message.toString() : err.toString(); | ||
const errorMessage = (err.message || err).toString(); | ||
// Mocks Lambda errors | ||
@@ -489,3 +515,2 @@ result = { | ||
serverlessLog(`Warning: No statusCode found for response "${responseName}".`); | ||
console.log(); | ||
} | ||
@@ -515,2 +540,4 @@ | ||
// Now we are outside of createLambdaContext, so this happens before the handler gets called: | ||
// We cannot use Hapijs's timeout feature because the logic above can take a significant time, so we implement it ourselves | ||
@@ -542,2 +569,3 @@ this.requests[requestId].timeout = setTimeout(this._replyTimeout.bind(this, response, funName, funTimeout, requestId), funTimeout); | ||
// All done, we can listen to incomming requests | ||
_listen() { | ||
@@ -547,6 +575,7 @@ this.server.start(err => { | ||
console.log(); | ||
serverlessLog(`Offline listening on ${this.options.httpsProtocol ? 'https' : 'http'}://localhost:${this.options.port}`); | ||
serverlessLog(`Offline listening on http${this.options.httpsProtocol ? 's' : ''}://localhost:${this.options.port}`); | ||
}); | ||
} | ||
// Bad news | ||
_reply500(response, message, err, requestId) { | ||
@@ -588,2 +617,21 @@ | ||
_create404Route() { | ||
this.server.route({ | ||
method: '*', | ||
path: '/{p*}', | ||
config: { cors: true }, | ||
handler: (request, reply) => { | ||
const response = reply({ | ||
statusCode: 404, | ||
error: 'Serverless-offline: route not found.', | ||
currentRoute: `${request.method} - ${request.path}`, | ||
existingRoutes: this.server.table()[0].table | ||
.filter(route => route.path !== '/{p*}') // Exclude this (404) route | ||
.sort((a, b) => a.path <= b.path ? -1 : 1) // Sort by path | ||
.map(route => `${route.method} - ${route.path}`), // Human-friendly result | ||
}); | ||
response.statusCode = 404; | ||
} | ||
}); | ||
} | ||
_logAndExit() { | ||
@@ -590,0 +638,0 @@ console.log.apply(null, arguments); |
@@ -6,2 +6,5 @@ 'use strict'; | ||
/* | ||
Just a wrapper around an external dependency for debugging purposes | ||
*/ | ||
module.exports = function jsonPath(json, path) { | ||
@@ -8,0 +11,0 @@ |
@@ -12,3 +12,3 @@ 'use strict'; | ||
/* | ||
Deeply traverses a plain object's keys (the serverless template, previously JSON) | ||
Deeply traverses a Serverless-style JSON (Velocity) template | ||
When it finds a string, assumes it's Velocity language and renders it. | ||
@@ -63,6 +63,7 @@ */ | ||
// Haaaa Velocity... this language does love strings a lot | ||
switch (renderResult) { | ||
case 'undefined': | ||
return undefined; | ||
return undefined; // But we don't, we want JavaScript types | ||
@@ -69,0 +70,0 @@ case 'null': |
40737
662
Updatedhapi@^13.3.0
Updatedlodash.isplainobject@^4.0.4