From 9ea1f981aa1d8944480806118e5d9e4af38c9525 Mon Sep 17 00:00:00 2001 From: William Chargin Date: Tue, 8 May 2018 16:09:37 -0700 Subject: [PATCH] Proxy Webpack dev server through to an API server (#245) Summary: This way, our frontend can talk to a backend that can read from the filesystem (among other things). Paired with @decentralion. Test Plan: ``` $ yarn backend $ SOURCECRED_DIRECTORY=/tmp/srccrd yarn start $ # verify that the browser looks good $ mkdir /tmp/srccrd $ echo hello >/tmp/srccrd/world $ curl localhost:3000/api/v1/data/world hello $ curl localhost:4000/api/v1/data/world hello ``` wchargin-branch: webpack-proxy --- config/paths.js | 1 + scripts/start.js | 117 +++++++++++++++++++++++++++++----------------- src/app/apiApp.js | 9 ++++ 3 files changed, 83 insertions(+), 44 deletions(-) create mode 100644 src/app/apiApp.js diff --git a/config/paths.js b/config/paths.js index a984d84..8e698ae 100644 --- a/config/paths.js +++ b/config/paths.js @@ -57,6 +57,7 @@ module.exports = { "commands/graph": resolveApp("src/cli/commands/graph.js"), "commands/plugin-graph": resolveApp("src/cli/commands/pluginGraph.js"), "commands/start": resolveApp("src/cli/commands/start.js"), + apiApp: resolveApp("src/app/apiApp.js"), sourcecred: resolveApp("src/cli/sourcecred.js"), fetchAndPrintGithubRepo: resolveApp( "src/plugins/github/bin/fetchAndPrintGithubRepo.js" diff --git a/scripts/start.js b/scripts/start.js index 07002fc..0698b23 100644 --- a/scripts/start.js +++ b/scripts/start.js @@ -15,6 +15,8 @@ process.on("unhandledRejection", (err) => { // Ensure environment variables are read. require("../config/env"); +const os = require("os"); +const path = require("path"); const fs = require("fs"); const chalk = require("chalk"); const webpack = require("webpack"); @@ -36,13 +38,20 @@ const useYarn = fs.existsSync(paths.yarnLockFile); const isInteractive = typeof process.stdout.isTTY !== "undefined" && process.stdout.isTTY; +// The following module is generated by running `yarn backend`. We want +// to be able to run Flow without having generated the module, and its +// types aren't very rich, anyway, so we stub out the type of `require` +// itself. +const apiApp = /*:: ((require: any) => */ require("../bin/apiApp") + .default /*:: )() */; + // Warn and crash if required files are missing if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { process.exit(1); } -// Tools like Cloud9 rely on this. -const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000; +const DEFAULT_WEBPACK_PORT = parseInt(process.env.PORT, 10) || 3000; +const DEFAULT_API_PORT = parseInt(process.env.PORT + 1000, 10) || 4000; const HOST = process.env.HOST || "0.0.0.0"; if (process.env.HOST) { @@ -62,48 +71,68 @@ if (process.env.HOST) { // We attempt to use the default port but if it is busy, we offer the user to // run on a different port. `choosePort()` Promise resolves to the next free port. -choosePort(HOST, DEFAULT_PORT) - .then((port) => { - if (port == null) { - // We have not found a port. - return; - } - const protocol = process.env.HTTPS === "true" ? "https" : "http"; - const appName = "sourcecred"; - const urls = prepareUrls(protocol, HOST, port); - // Create a webpack compiler that is configured with custom messages. - const compiler = createCompiler(webpack, config, appName, urls, useYarn); - // Load proxy config - const proxySetting = undefined; - const proxyConfig = prepareProxy(proxySetting, paths.appPublic); - // Serve webpack assets generated by the compiler over a web sever. - const serverConfig = createDevServerConfig( - proxyConfig, - urls.lanUrlForConfig - ); - const devServer = new WebpackDevServer(compiler, serverConfig); - // Launch WebpackDevServer. - devServer.listen(port, HOST, (err) => { - if (err) { - return console.log(err); - } - if (isInteractive) { - clearConsole(); - } - console.log(chalk.cyan("Starting the development server...\n")); - openBrowser(urls.localUrlForBrowser); - }); +async function main() { + const webpackPort = await choosePort(HOST, DEFAULT_WEBPACK_PORT); + const apiPort = await choosePort(HOST, DEFAULT_API_PORT); - ["SIGINT", "SIGTERM"].forEach(function(sig) { - process.on(sig, function() { - devServer.close(); - process.exit(); - }); + if (webpackPort == null) { + console.error("Could not find a port for the Webpack server."); + } + if (apiPort == null) { + console.error("Could not find a port for the API server."); + } + + const sourcecredDirectory = + process.env.SOURCECRED_DIRECTORY || path.join(os.tmpdir(), "sourcecred"); + const apiServer = await new Promise(async (resolve, _unused_reject) => { + let server = apiApp(sourcecredDirectory).listen(apiPort, () => { + resolve(server); }); - }) - .catch((err) => { - if (err && err.message) { - console.log(err.message); - } - process.exit(1); }); + console.log( + chalk.green(`Server listening on port ${apiServer.address().port}.`) + ); + console.log(); + + const protocol = process.env.HTTPS === "true" ? "https" : "http"; + const appName = "sourcecred"; + const urls = prepareUrls(protocol, HOST, webpackPort); + // Create a webpack compiler that is configured with custom messages. + const compiler = createCompiler(webpack, config, appName, urls, useYarn); + // Load proxy config + const proxySetting = { + "/api": { + target: `${protocol}://localhost:${apiServer.address().port}`, + }, + }; + const proxyConfig = prepareProxy(proxySetting, paths.appPublic); + // Serve webpack assets generated by the compiler over a web sever. + const serverConfig = createDevServerConfig(proxyConfig, urls.lanUrlForConfig); + const devServer = new WebpackDevServer(compiler, serverConfig); + // Launch WebpackDevServer. + devServer.listen(webpackPort, HOST, (err) => { + if (err) { + return console.log(err); + } + if (isInteractive) { + clearConsole(); + } + console.log(chalk.cyan("Starting the development server...\n")); + openBrowser(urls.localUrlForBrowser); + }); + + ["SIGINT", "SIGTERM"].forEach(function(sig) { + process.on(sig, function() { + devServer.close(); + apiServer.close(); + process.exit(); + }); + }); +} + +main().catch((err) => { + if (err && err.message) { + console.log(err.message); + } + process.exit(1); +}); diff --git a/src/app/apiApp.js b/src/app/apiApp.js new file mode 100644 index 0000000..69eb97d --- /dev/null +++ b/src/app/apiApp.js @@ -0,0 +1,9 @@ +// @flow + +import express from "express"; + +export default function apiApp(sourcecredDirectory: string) { + const app = express(); + app.use("/api/v1/data", express.static(sourcecredDirectory)); + return app; +}