forked from nextstrain/auspice
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathview.js
156 lines (140 loc) · 6.69 KB
/
view.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
/* eslint no-console: off */
/* eslint global-require: off */
const path = require("path");
const fs = require("fs");
const express = require("express");
const expressStaticGzip = require("express-static-gzip");
const compression = require('compression');
const nakedRedirect = require('express-naked-redirect');
const utils = require("./utils");
const version = require('../src/version').version;
const chalk = require('chalk');
const SUPPRESS = require('argparse').Const.SUPPRESS;
const addParser = (parser) => {
const description = `Launch a local server to view locally available datasets & narratives.
The handlers for (auspice) client requests can be overridden here (see documentation for more details).
If you want to serve a customised auspice client then you must have run "auspice build" in the same directory
as you run "auspice view" from.
`;
const subparser = parser.addParser('view', {addHelp: true, description});
subparser.addArgument('--verbose', {action: "storeTrue", help: "Print more verbose logging messages."});
subparser.addArgument('--handlers', {action: "store", metavar: "JS", help: "Overwrite the provided server handlers for client requests. See documentation for more details."});
subparser.addArgument('--datasetDir', {metavar: "PATH", help: "Directory where datasets (JSONs) are sourced. This is ignored if you define custom handlers."});
subparser.addArgument('--narrativeDir', {metavar: "PATH", help: "Directory where narratives (Markdown files) are sourced. This is ignored if you define custom handlers."});
/* there are some options which we deliberately do not document via `--help`. */
subparser.addArgument('--customBuild', {action: "storeTrue", help: SUPPRESS}); /* see printed warning in the code below */
subparser.addArgument('--gh-pages', {action: "store", help: SUPPRESS}); /* related to the "static-site-generation" or "github-pages" */
};
const serveRelativeFilepaths = ({app, dir}) => {
app.get("*.json", (req, res) => {
const filePath = path.join(dir, req.originalUrl);
utils.log(`${req.originalUrl} -> ${filePath}`);
res.sendFile(filePath);
});
return `JSON requests will be served relative to ${dir}.`;
};
const loadAndAddHandlers = ({app, handlersArg, datasetDir, narrativeDir}) => {
/* load server handlers, either from provided path or the defaults */
const handlers = {};
let datasetsPath, narrativesPath;
if (handlersArg) {
const handlersPath = path.resolve(handlersArg);
utils.verbose(`Loading handlers from ${handlersPath}`);
const inject = require(handlersPath); // eslint-disable-line
handlers.getAvailable = inject.getAvailable;
handlers.getDataset = inject.getDataset;
handlers.getNarrative = inject.getNarrative;
} else {
datasetsPath = utils.resolveLocalDirectory(datasetDir, false);
narrativesPath = utils.resolveLocalDirectory(narrativeDir, true);
handlers.getAvailable = require("./server/getAvailable")
.setUpGetAvailableHandler({datasetsPath, narrativesPath});
handlers.getDataset = require("./server/getDataset")
.setUpGetDatasetHandler({datasetsPath});
handlers.getNarrative = require("./server/getNarrative")
.setUpGetNarrativeHandler({narrativesPath});
}
/* apply handlers */
app.get("/charon/getAvailable", handlers.getAvailable);
app.get("/charon/getDataset", handlers.getDataset);
app.get("/charon/getNarrative", handlers.getNarrative);
app.get("/charon*", (req, res) => {
res.statusMessage = "Query unhandled -- " + req.originalUrl;
utils.warn(res.statusMessage);
return res.status(500).end();
});
return handlersArg ?
`Custom server handlers provided.` :
`Looking for datasets in ${datasetsPath}\nLooking for narratives in ${narrativesPath}`;
};
const getAuspiceBuild = () => {
const cwd = path.resolve(process.cwd());
const sourceDir = path.resolve(__dirname, "..");
if (
cwd !== sourceDir &&
fs.existsSync(path.join(cwd, "index.html")) &&
fs.existsSync(path.join(cwd, "dist")) &&
fs.existsSync(path.join(cwd, "dist", "auspice.bundle.js"))
) {
return {
message: "Serving the auspice build which exists in this directory.",
baseDir: cwd,
distDir: path.join(cwd, "dist")
};
}
return {
message: `Serving auspice version ${version}`,
baseDir: sourceDir,
distDir: path.join(sourceDir, "dist")
};
};
const run = (args) => {
/* Basic server set up */
const app = express();
app.set('port', process.env.PORT || 4000);
app.set('host', process.env.HOST || "localhost");
app.use(compression());
app.use(nakedRedirect({reverse: true})); /* redirect www.name.org to name.org */
if (args.customBuild) {
utils.warn("--customBuild is no longer used and will be removed in a future version. We now serve a custom auspice build if one exists in the directory `auspice view` is run from");
}
const auspiceBuild = getAuspiceBuild();
utils.verbose(`Serving index / favicon etc from "${auspiceBuild.baseDir}"`);
utils.verbose(`Serving built javascript from "${auspiceBuild.distDir}"`);
app.get("/favicon.png", (req, res) => {res.sendFile(path.join(auspiceBuild.baseDir, "favicon.png"));});
app.use("/dist", expressStaticGzip(auspiceBuild.distDir));
app.use(express.static(auspiceBuild.distDir));
let handlerMsg = "";
if (args.gh_pages) {
handlerMsg = serveRelativeFilepaths({app, dir: path.resolve(args.gh_pages)});
} else {
handlerMsg = loadAndAddHandlers({app, handlersArg: args.handlers, datasetDir: args.datasetDir, narrativeDir: args.narrativeDir});
}
/* this must be the last "get" handler, else the "*" swallows all other requests */
app.get("*", (req, res) => {
res.sendFile(path.join(auspiceBuild.baseDir, "index.html"));
});
const server = app.listen(app.get('port'), app.get('host'), () => {
utils.log("\n\n---------------------------------------------------");
const host = app.get('host');
const {port} = server.address();
console.log(chalk.blueBright("Auspice server now running at ") + chalk.blueBright.underline.bold(`http://${host}:${port}`));
utils.log(auspiceBuild.message);
utils.log(handlerMsg);
utils.log("---------------------------------------------------\n\n");
}).on('error', (err) => {
if (err.code === 'EADDRINUSE') {
utils.error(`Port ${app.get('port')} is currently in use by another program.
You must either close that program or specify a different port by setting the shell variable
"$PORT". Note that on MacOS / Linux, "lsof -n -i :${app.get('port')} | grep LISTEN" should
identify the process currently using the port.`);
}
utils.error(`Uncaught error in app.listen(). Code: ${err.code}`);
});
};
module.exports = {
addParser,
run,
loadAndAddHandlers,
serveRelativeFilepaths
};