Add `sourcecred start` (#234)

Summary:
We need a way for our web applications to interact with data on the
filesystem. In this commit, we introduce a webserver that serves
statically from two directory trees: first, the result of a live-updated
Webpack build; second, the SourceCred data directory.

Test Plan:
Run `yarn backend` and `node ./bin/sourcecred.js start`. When ready,
navigate to the server’s root route in a web browser. Note that a nice
React app is displayed. Then, change something in that React app source.
Note that the server console displays Webpack’s update messages, and
that refreshing the page in the browser renders the new version of the
app. Finally, visit

    /__data__/graphs/sourcecred/example-github/graph.json

in the browser to see the graph for the example repository, assuming
that you had generated its graph previously.

wchargin-branch: start
This commit is contained in:
William Chargin 2018-05-07 20:10:49 -07:00 committed by GitHub
parent 18ddbfff3e
commit 57682065fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 118 additions and 0 deletions

View File

@ -61,6 +61,7 @@ module.exports = {
"commands/combine": resolveApp("src/cli/commands/combine.js"), "commands/combine": resolveApp("src/cli/commands/combine.js"),
"commands/graph": resolveApp("src/cli/commands/graph.js"), "commands/graph": resolveApp("src/cli/commands/graph.js"),
"commands/plugin-graph": resolveApp("src/cli/commands/pluginGraph.js"), "commands/plugin-graph": resolveApp("src/cli/commands/pluginGraph.js"),
"commands/start": resolveApp("src/cli/commands/start.js"),
sourcecred: resolveApp("src/cli/sourcecred.js"), sourcecred: resolveApp("src/cli/sourcecred.js"),
fetchAndPrintGithubRepo: resolveApp( fetchAndPrintGithubRepo: resolveApp(
"src/plugins/github/bin/fetchAndPrintGithubRepo.js" "src/plugins/github/bin/fetchAndPrintGithubRepo.js"

117
src/cli/commands/start.js Normal file
View File

@ -0,0 +1,117 @@
// @flow
import {Command} from "@oclif/command";
import chalk from "chalk";
import child_process from "child_process";
import express from "express";
import {choosePort} from "react-dev-utils/WebpackDevServerUtils";
import tmp from "tmp";
import {sourcecredDirectoryFlag} from "../common";
// Makes the script crash on unhandled rejections instead of silently
// ignoring them. In the future, promise rejections that are not handled will
// terminate the Node.js process with a non-zero exit code.
process.on("unhandledRejection", (err) => {
throw err;
});
const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 4000;
const HOST = process.env.HOST || "0.0.0.0";
export default class StartCommand extends Command {
static description = "start a web server to explore the contribution graph";
static flags = {
"sourcecred-directory": sourcecredDirectoryFlag(),
};
async run() {
const {flags: {"sourcecred-directory": sourcecredDirectory}} = this.parse(
StartCommand
);
startServer(sourcecredDirectory);
}
}
async function startServer(sourcecredDirectory: string) {
let server, webpack;
function cleanup() {
if (server && server.listening) {
server.close();
}
if (webpack) {
webpack.kill();
}
}
let shuttingDown = false;
["SIGINT", "SIGTERM"].forEach((signal) => {
process.on(signal, () => {
if (shuttingDown) {
// Force shut-down.
process.exit(2);
} else {
shuttingDown = true;
console.log("\nShutting down.");
cleanup();
}
});
});
const webpackWorkdir = tmp.dirSync({unsafeCleanup: true}).name;
console.log(chalk.bold("Starting Express..."));
const expressApp = createExpressApp(webpackWorkdir, sourcecredDirectory);
server = await new Promise(async (resolve, _unused_reject) => {
const port = await choosePort(HOST, DEFAULT_PORT);
let server = expressApp.listen(port, () => {
resolve(server);
});
});
server.on("close", () => {
console.log(chalk.bold("Express server closed."));
cleanup();
});
console.log(
chalk.green(`Server listening on port ${server.address().port}. `) +
`You might want to wait for Webpack to say ${chalk.bold("[built]")}.`
);
console.log();
console.log(chalk.bold("Starting Webpack..."));
webpack = startWebpack(webpackWorkdir);
webpack.on("exit", (code, signal) => {
console.log(
`${chalk.bold("Webpack exited")} with ${code} (signal: ${signal})`
);
cleanup();
});
}
function createExpressApp(webpackWorkdir, sourcecredDirectory) {
const app = express();
app.use(express.static(webpackWorkdir));
app.use("/__data__", express.static(sourcecredDirectory));
return app;
}
function startWebpack(workdir: string) {
const webpack = child_process.spawn(
"yarn",
[
"--silent",
"webpack",
"--config",
"./config/webpack.config.dev.js",
"--output-path",
workdir,
"--watch",
],
{
env: {...process.env, NODE_ENV: "development"},
stdio: "inherit",
}
);
return webpack;
}