Skip to content
This repository was archived by the owner on Sep 12, 2019. It is now read-only.

Commit 27da118

Browse files
author
sw-yx
committed
move serveFunctions into netlify dev plugin from ZISI
1 parent 8b03110 commit 27da118

File tree

4 files changed

+249
-26
lines changed

4 files changed

+249
-26
lines changed

package-lock.json

+29-24
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,17 @@
1111
"@oclif/command": "^1",
1212
"@oclif/config": "^1",
1313
"ascii-table": "0.0.9",
14+
"body-parser": "^1.18.3",
1415
"boxen": "^3.0.0",
1516
"chalk": "^2.4.2",
1617
"chokidar": "^2.1.5",
1718
"copy-template-dir": "^1.4.0",
1819
"execa": "^1.0.0",
20+
"express": "^4.16.4",
21+
"express-logging": "^1.1.1",
1922
"fs-extra": "^7.0.1",
2023
"fuzzy": "^0.1.3",
21-
"get-port": "^4.1.0",
24+
"get-port": "^4.2.0",
2225
"gh-release-fetch": "^1.0.3",
2326
"http-proxy": "^1.17.0",
2427
"inquirer": "^6.2.2",
@@ -27,6 +30,7 @@
2730
"netlify-cli-logo": "^1.0.0",
2831
"node-fetch": "^2.3.0",
2932
"ora": "^3.4.0",
33+
"querystring": "^0.2.0",
3034
"safe-join": "^0.1.2",
3135
"static-server": "^2.2.1",
3236
"wait-port": "^0.2.2",

src/commands/dev/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const httpProxy = require("http-proxy");
55
const waitPort = require("wait-port");
66
const getPort = require("get-port");
77
const chokidar = require("chokidar");
8-
const { serveFunctions } = require("@netlify/zip-it-and-ship-it");
8+
const { serveFunctions } = require("../../utils/serveFunctions");
99
const { serverSettings } = require("../../detect-server");
1010
const { detectFunctionsBuilder } = require("../../detect-functions-builder");
1111
const Command = require("@netlify/cli-utils");

src/utils/serveFunctions.js

+214
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
const fs = require("fs");
2+
const express = require("express");
3+
const bodyParser = require("body-parser");
4+
const expressLogging = require("express-logging");
5+
const queryString = require("querystring");
6+
const path = require("path");
7+
const getPort = require("get-port");
8+
const chokidar = require("chokidar");
9+
const chalk = require("chalk");
10+
11+
const NETLIFYDEVLOG = `${chalk.greenBright("◈")}`;
12+
const NETLIFYDEVWARN = `${chalk.yellowBright("◈")}`;
13+
const NETLIFYDEVERR = `${chalk.redBright("◈")}`;
14+
15+
const { findModuleDir, findHandler } = require("./finders");
16+
17+
const defaultPort = 34567;
18+
19+
function handleErr(err, response) {
20+
response.statusCode = 500;
21+
response.write(
22+
`${NETLIFYDEVERR} Function invocation failed: ` + err.toString()
23+
);
24+
response.end();
25+
console.log(`${NETLIFYDEVERR} Error during invocation: `, err);
26+
return;
27+
}
28+
29+
function createCallback(response) {
30+
return function callback(err, lambdaResponse) {
31+
if (err) {
32+
return handleErr(err, response);
33+
}
34+
35+
response.statusCode = lambdaResponse.statusCode;
36+
for (const key in lambdaResponse.headers) {
37+
response.setHeader(key, lambdaResponse.headers[key]);
38+
}
39+
response.write(
40+
lambdaResponse.isBase64Encoded
41+
? Buffer.from(lambdaResponse.body, "base64")
42+
: lambdaResponse.body
43+
);
44+
response.end();
45+
};
46+
}
47+
48+
function promiseCallback(promise, callback) {
49+
if (!promise) return;
50+
if (typeof promise.then !== "function") return;
51+
if (typeof callback !== "function") return;
52+
53+
promise.then(
54+
function(data) {
55+
callback(null, data);
56+
},
57+
function(err) {
58+
callback(err, null);
59+
}
60+
);
61+
}
62+
63+
function getHandlerPath(functionPath) {
64+
if (functionPath.match(/\.js$/)) {
65+
return functionPath;
66+
}
67+
68+
return path.join(functionPath, `${path.basename(functionPath)}.js`);
69+
}
70+
71+
function createHandler(dir, options) {
72+
const functions = {};
73+
fs.readdirSync(dir).forEach(file => {
74+
if (dir === "node_modules") {
75+
return;
76+
}
77+
const functionPath = path.resolve(path.join(dir, file));
78+
const handlerPath = findHandler(functionPath);
79+
if (!handlerPath) {
80+
return;
81+
}
82+
if (path.extname(functionPath) === ".js") {
83+
functions[file.replace(/\.js$/, "")] = {
84+
functionPath,
85+
moduleDir: findModuleDir(functionPath)
86+
};
87+
} else if (fs.lstatSync(functionPath).isDirectory()) {
88+
functions[file] = {
89+
functionPath: handlerPath,
90+
moduleDir: findModuleDir(functionPath)
91+
};
92+
}
93+
});
94+
95+
Object.keys(functions).forEach(name => {
96+
const fn = functions[name];
97+
const clearCache = () => {
98+
const before = module.paths;
99+
module.paths = [fn.moduleDir];
100+
delete require.cache[require.resolve(fn.functionPath)];
101+
module.paths = before;
102+
};
103+
fn.watcher = chokidar.watch(
104+
[fn.functionPath, path.join(fn.moduleDir, "package.json")],
105+
{
106+
ignored: /node_modules/
107+
}
108+
);
109+
fn.watcher
110+
.on("add", clearCache)
111+
.on("change", clearCache)
112+
.on("unlink", clearCache);
113+
});
114+
115+
return function(request, response) {
116+
// handle proxies without path re-writes (http-servr)
117+
const cleanPath = request.path.replace(/^\/.netlify\/functions/, "");
118+
119+
const func = cleanPath.split("/").filter(function(e) {
120+
return e;
121+
})[0];
122+
if (!functions[func]) {
123+
response.statusCode = 404;
124+
response.end("Function not found...");
125+
return;
126+
}
127+
const { functionPath, moduleDir } = functions[func];
128+
let handler;
129+
let before = module.paths;
130+
try {
131+
module.paths = [moduleDir];
132+
handler = require(functionPath);
133+
module.paths = before;
134+
} catch (err) {
135+
module.paths = before;
136+
handleErr(err, response);
137+
return;
138+
}
139+
140+
const isBase64 =
141+
request.body &&
142+
!(request.headers["content-type"] || "").match(
143+
/text|application|multipart\/form-data/
144+
);
145+
const lambdaRequest = {
146+
path: request.path,
147+
httpMethod: request.method,
148+
queryStringParameters: queryString.parse(request.url.split(/\?(.+)/)[1]),
149+
headers: request.headers,
150+
body: isBase64
151+
? Buffer.from(request.body.toString(), "utf8").toString("base64")
152+
: request.body,
153+
isBase64Encoded: isBase64
154+
};
155+
156+
const callback = createCallback(response);
157+
const promise = handler.handler(lambdaRequest, {}, callback);
158+
promiseCallback(promise, callback);
159+
};
160+
}
161+
162+
async function serveFunctions(settings, options) {
163+
options = options || {};
164+
const app = express();
165+
const dir = settings.functionsDir;
166+
const port = await getPort({
167+
port: assignLoudly(settings.port, defaultPort)
168+
});
169+
170+
app.use(bodyParser.raw({ limit: "6mb" }));
171+
app.use(bodyParser.text({ limit: "6mb", type: "*/*" }));
172+
app.use(
173+
expressLogging(console, {
174+
blacklist: ["/favicon.ico"]
175+
})
176+
);
177+
178+
app.get("/favicon.ico", function(req, res) {
179+
res.status(204).end();
180+
});
181+
app.all("*", createHandler(dir, options));
182+
183+
app.listen(port, function(err) {
184+
if (err) {
185+
console.error(`${NETLIFYDEVERR} Unable to start lambda server: `, err);
186+
process.exit(1);
187+
}
188+
189+
// add newline because this often appears alongside the client devserver's output
190+
console.log(`\n${NETLIFYDEVLOG} Lambda server is listening on ${port}`);
191+
});
192+
193+
return Promise.resolve({
194+
port
195+
});
196+
}
197+
198+
module.exports = { serveFunctions };
199+
200+
// if first arg is undefined, use default, but tell user about it in case it is unintentional
201+
function assignLoudly(
202+
optionalValue,
203+
fallbackValue,
204+
tellUser = dV =>
205+
console.log(`${NETLIFYDEVLOG} No port specified, using defaultPort of `, dV)
206+
) {
207+
if (fallbackValue === undefined) throw new Error("must have a fallbackValue");
208+
if (fallbackValue !== optionalValue && optionalValue === undefined) {
209+
tellUser(fallbackValue);
210+
return fallbackValue;
211+
} else {
212+
return optionalValue;
213+
}
214+
}

0 commit comments

Comments
 (0)