UNPKG

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