diff options
| author | Kevin J Hoerr <kjhoerr@protonmail.com> | 2020-04-26 21:53:26 -0400 |
|---|---|---|
| committer | Kevin J Hoerr <kjhoerr@protonmail.com> | 2020-04-26 21:53:26 -0400 |
| commit | ddecabba54eb24ab4ac07a67621f805d3ad9e2ce (patch) | |
| tree | 7422ecca20f3a66ce1e6bc2839db454eacc1fbd2 /src | |
| parent | ee068fcbf0f409ac91d6559438e84fb89e654a15 (diff) | |
| download | ao-coverage-ddecabba54eb24ab4ac07a67621f805d3ad9e2ce.tar.gz ao-coverage-ddecabba54eb24ab4ac07a67621f805d3ad9e2ce.tar.bz2 ao-coverage-ddecabba54eb24ab4ac07a67621f805d3ad9e2ce.zip | |
These changes moved a lot of the startup async to run in a streamlined
async init function. This brings more logic "to light", so it should
probably have unit tests added to check the edge cases. As a bonus, no
async runs as a result of route initialization.
Speaking of routes, it might be nice to trim down the route calls
themselves with async functions, if possible. The upload routes in
particular use a lot of async. Just a note for the future.
Diffstat (limited to 'src')
| -rw-r--r-- | src/formats.ts | 10 | ||||
| -rw-r--r-- | src/index.ts | 82 | ||||
| -rw-r--r-- | src/routes.test.ts | 217 | ||||
| -rw-r--r-- | src/routes.ts | 58 | ||||
| -rw-r--r-- | src/util/config.test.ts | 10 | ||||
| -rw-r--r-- | src/util/config.ts | 75 |
6 files changed, 302 insertions, 150 deletions
diff --git a/src/formats.ts b/src/formats.ts index d22ea3e..7638399 100644 --- a/src/formats.ts +++ b/src/formats.ts @@ -32,12 +32,12 @@ export const defaultColorMatches = ( coverage >= style.stage1 ? 76 : coverage >= style.stage2 - ? Math.floor( + ? Math.floor( ((style.stage1 - coverage) / (style.stage1 - style.stage2)) * 10 ) * - 16 + + 16 + 76 - : 225 + Math.floor(coverage / (style.stage2 / 11)); + : 225 + Math.floor(coverage / (style.stage2 / 11)); const result = gradient.toString(16); return (result.length === 1 ? "0" : "") + result + "1"; }; @@ -75,11 +75,11 @@ const FormatsObj: FormatObj = { } }, - listFormats: function () { + listFormats: function() { return Object.keys(this.formats); }, - getFormat: function (format: string) { + getFormat: function(format: string) { return this.formats[format]; } }; diff --git a/src/index.ts b/src/index.ts index 76620e8..e4f8db1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,9 +1,5 @@ import dotenv from "dotenv"; import express from "express"; -import { MongoClient } from "mongodb"; -import path from "path"; -import fs from "fs"; - import winston from "winston"; import expressWinston from "express-winston"; @@ -12,57 +8,41 @@ dotenv.config(); import routes from "./routes"; import Metadata from "./metadata"; import loggerConfig from "./util/logger"; -import { configOrError, handleShutdown } from "./util/config"; +import { handleStartup, handleShutdown } from "./util/config"; // Start-up configuration const BIND_ADDRESS = process.env.BIND_ADDRESS ?? "localhost"; +const MONGO_DB = process.env.MONGO_DB ?? "ao-coverage"; const PORT = Number(process.env.PORT ?? 3000); const logger = winston.createLogger(loggerConfig("ROOT")); -const MONGO_URI = configOrError("MONGO_URI"); -const MONGO_DB = process.env.MONGO_DB ?? "ao-coverage"; -const HOST_DIR = configOrError("HOST_DIR"); - -fs.accessSync(HOST_DIR, fs.constants.R_OK | fs.constants.W_OK); -if (!path.isAbsolute(HOST_DIR)) { - logger.error("HOST_DIR must be an absolute path"); - process.exit(1); -} - -new MongoClient(MONGO_URI, { useUnifiedTopology: true }).connect( - (err, mongo) => { - if (err !== null) { - logger.error(err ?? "Unable to connect to database"); - process.exit(1); - } - - const app: express.Application = express(); - const metadata = new Metadata(mongo.db(MONGO_DB)); - - app.use( - expressWinston.logger({ - ...loggerConfig("HTTP"), - colorize: true, - // filter out token query param from URL - msg: - '{{req.method}} {{req.url.replace(/token=[-\\w.~]*(&*)/, "token=$1")}} - {{res.statusCode}} {{res.responseTime}}ms' - }) - ); - - // actual app routes - app.use(routes(metadata)); - - app.use(expressWinston.errorLogger(loggerConfig("_ERR"))); - - const server = app.listen(PORT, BIND_ADDRESS, () => { - logger.info("Express has started: http://%s:%d/", BIND_ADDRESS, PORT); - }); - - // application exit handling - const signalCodes: NodeJS.Signals[] = ["SIGTERM", "SIGHUP", "SIGINT"]; - signalCodes.map((code: NodeJS.Signals) => { - process.on(code, handleShutdown(mongo, server)); - }); - } -); +handleStartup().then(mongo => { + const app: express.Application = express(); + const metadata = new Metadata(mongo.db(MONGO_DB)); + + app.use( + expressWinston.logger({ + ...loggerConfig("HTTP"), + colorize: true, + // filter out token query param from URL + msg: + '{{req.method}} {{req.url.replace(/token=[-\\w.~]*(&*)/, "token=$1")}} - {{res.statusCode}} {{res.responseTime}}ms' + }) + ); + + // actual app routes + app.use(routes(metadata)); + + app.use(expressWinston.errorLogger(loggerConfig("_ERR"))); + + const server = app.listen(PORT, BIND_ADDRESS, () => { + logger.info("Express has started: http://%s:%d/", BIND_ADDRESS, PORT); + }); + + // application exit handling + const signalCodes: NodeJS.Signals[] = ["SIGTERM", "SIGHUP", "SIGINT"]; + signalCodes.map((code: NodeJS.Signals) => { + process.on(code, handleShutdown(mongo, server)); + }); +}); diff --git a/src/routes.test.ts b/src/routes.test.ts index b4979d6..3579011 100644 --- a/src/routes.test.ts +++ b/src/routes.test.ts @@ -1,4 +1,4 @@ -import _request from "supertest"; +import _request, { SuperTest, Test } from "supertest"; import express from "express"; import dotenv from "dotenv"; import fs from "fs"; @@ -8,9 +8,10 @@ dotenv.config(); process.env.UPLOAD_LIMIT = "40000"; -import { configOrError } from "./util/config"; +import { configOrError, persistTemplate } from "./util/config"; import routes from "./routes"; import Metadata from "./metadata"; +import { Template } from "./templates"; import { Db } from "mongodb"; import { badgen } from "badgen"; import { BranchNotFoundError } from "./errors"; @@ -22,28 +23,44 @@ type MetadataMockType = { createRepository: jest.Mock; }; -const mock = (headCommit: jest.Mock = jest.fn(() => new Promise(solv => solv("testcommit"))), updateBranch: jest.Mock = jest.fn(() => new Promise(solv => solv(true)))): MetadataMockType => ({ +const mock = ( + headCommit: jest.Mock = jest.fn( + () => new Promise(solv => solv("testcommit")) + ), + updateBranch: jest.Mock = jest.fn(() => new Promise(solv => solv(true))) +): MetadataMockType => ({ database: {} as Db, getHeadCommit: headCommit, updateBranch: updateBranch, createRepository: jest.fn() }); -const request = (mockMeta: MetadataMockType = mock()) => { +const request = (mockMeta: MetadataMockType = mock()): SuperTest<Test> => { const app = express(); app.use(routes(mockMeta as Metadata)); return _request(app); -} +}; const HOST_DIR = configOrError("HOST_DIR"); const TARGET_URL = process.env.TARGET_URL ?? "http://localhost:3000"; const TOKEN = process.env.TOKEN ?? ""; describe("templates", () => { - describe("GET /bash", () => { - it("should return the bash file containing tbe curl command", async () => { + it("should return the bash file containing the curl command", async () => { + await persistTemplate({ + inputFile: path.join( + __dirname, + "..", + "public", + "templates", + "bash.template" + ), + outputFile: path.join(HOST_DIR, "bash"), + context: { TARGET_URL } + } as Template); + const res = await request() .get("/bash") .expect(200); @@ -54,20 +71,69 @@ describe("templates", () => { describe("GET /", () => { it("should return the index HTML file containing the bash command", async () => { + await persistTemplate({ + inputFile: path.join( + __dirname, + "..", + "public", + "templates", + "index.html.template" + ), + outputFile: path.join(HOST_DIR, "index.html"), + context: { TARGET_URL } + } as Template); + const res = await request() .get("/") .expect("Content-Type", /html/) .expect(200); expect(res.text).toMatch(`bash <(curl -s ${TARGET_URL}/bash)`); - }) + }); }); }); -describe("Badges and reports", () => { +describe("Static files", () => { + const staticRoot = path.join(__dirname, "..", "public", "static"); + + it("should return favicon.ico at GET /favicon.ico", async () => { + const buffer = await fs.promises.readFile( + path.join(staticRoot, "favicon.ico") + ); + + await request() + .get("/favicon.ico") + .expect("Content-Type", /icon/) + .expect(buffer) + .expect(200); + }); + + it("should return index.css at GET /static/index.css", async () => { + const buffer = await fs.promises.readFile( + path.join(staticRoot, "index.css") + ); + + const res = await request() + .get("/static/index.css") + .expect(200); + expect(res.text).toEqual(buffer.toString("utf-8")); + }); +}); - const report_path = path.join(HOST_DIR, "testorg", "testrepo", "testbranch", "testcommit"); - const actual_report = path.join(__dirname, "..", "example_reports", "tarpaulin-report.html"); - const fake_badge = badgen({ +describe("Badges and reports", () => { + const reportPath = path.join( + HOST_DIR, + "testorg", + "testrepo", + "testbranch", + "testcommit" + ); + const actualReport = path.join( + __dirname, + "..", + "example_reports", + "tarpaulin-report.html" + ); + const fakeBadge = badgen({ label: "coverage", status: "120%", color: "#E1C" @@ -75,9 +141,12 @@ describe("Badges and reports", () => { beforeAll(async () => { // place test files on HOST_DIR - await fs.promises.mkdir(report_path, { recursive: true }); - await fs.promises.copyFile(actual_report, path.join(report_path, "index.html")); - await fs.promises.writeFile(path.join(report_path, "badge.svg"), fake_badge); + await fs.promises.mkdir(reportPath, { recursive: true }); + await fs.promises.copyFile( + actualReport, + path.join(reportPath, "index.html") + ); + await fs.promises.writeFile(path.join(reportPath, "badge.svg"), fakeBadge); }); describe("GET /v1/:org/:repo/:branch/:commit.html", () => { @@ -86,13 +155,15 @@ describe("Badges and reports", () => { .get("/v1/testorg/testrepo/testbranch/testcommit.html") .expect("Content-Type", /html/) .expect(200); - const buffer = await fs.promises.readFile(actual_report); + const buffer = await fs.promises.readFile(actualReport); expect(res.text).toEqual(buffer.toString("utf-8")); }); it("should return 404 if file does not exist", async () => { - await request().get("/v1/neorg/nerepo/nebranch/necommit.html").expect(404); + await request() + .get("/v1/neorg/nerepo/nebranch/necommit.html") + .expect(404); }); }); @@ -103,24 +174,32 @@ describe("Badges and reports", () => { .get("/v1/testorg/testrepo/testbranch.html") .expect("Content-Type", /html/) .expect(200); - const buffer = await fs.promises.readFile(actual_report); + const buffer = await fs.promises.readFile(actualReport); expect(mockMeta.getHeadCommit).toHaveBeenCalledTimes(1); expect(res.text).toEqual(buffer.toString("utf-8")); }); it("should return 404 if file does not exist", async () => { - await request().get("/v1/neorg/nerepo/nebranch.html").expect(404); + await request() + .get("/v1/neorg/nerepo/nebranch.html") + .expect(404); }); it("should return 404 if head commit not found", async () => { - const head = jest.fn(() => new Promise(solv => solv(new BranchNotFoundError()))); - await request(mock(head)).get("/v1/testorg/testrepo/testbranch.html").expect(404); + const head = jest.fn( + () => new Promise(solv => solv(new BranchNotFoundError())) + ); + await request(mock(head)) + .get("/v1/testorg/testrepo/testbranch.html") + .expect(404); }); it("should return 500 if promise is rejected", async () => { const head = jest.fn(() => new Promise((_, rej) => rej("fooey"))); - await request(mock(head)).get("/v1/testorg/testrepo/testbranch.html").expect(500); + await request(mock(head)) + .get("/v1/testorg/testrepo/testbranch.html") + .expect(500); }); }); @@ -131,11 +210,13 @@ describe("Badges and reports", () => { .expect("Content-Type", /svg/) .expect(200); - expect(res.body.toString("utf-8")).toEqual(fake_badge); + expect(res.body.toString("utf-8")).toEqual(fakeBadge); }); it("should return 404 if file does not exist", async () => { - await request().get("/v1/neorg/nerepo/nebranch/necommit.svg").expect(404); + await request() + .get("/v1/neorg/nerepo/nebranch/necommit.svg") + .expect(404); }); }); @@ -148,82 +229,124 @@ describe("Badges and reports", () => { .expect(200); expect(mockMeta.getHeadCommit).toHaveBeenCalledTimes(1); - expect(res.body.toString("utf-8")).toEqual(fake_badge); + expect(res.body.toString("utf-8")).toEqual(fakeBadge); }); it("should return 404 if file does not exist", async () => { - await request().get("/v1/neorg/nerepo/nebranch.svg").expect(404); + await request() + .get("/v1/neorg/nerepo/nebranch.svg") + .expect(404); }); it("should return 404 if head commit not found", async () => { - const head = jest.fn(() => new Promise(solv => solv(new BranchNotFoundError()))); - await request(mock(head)).get("/v1/testorg/testrepo/testbranch.svg").expect(404); + const head = jest.fn( + () => new Promise(solv => solv(new BranchNotFoundError())) + ); + await request(mock(head)) + .get("/v1/testorg/testrepo/testbranch.svg") + .expect(404); }); it("should return 500 if promise is rejected", async () => { const head = jest.fn(() => new Promise((_, rej) => rej("fooey"))); - await request(mock(head)).get("/v1/testorg/testrepo/testbranch.svg").expect(500); + await request(mock(head)) + .get("/v1/testorg/testrepo/testbranch.svg") + .expect(500); }); }); }); describe("Uploads", () => { - - const report_path = path.join(HOST_DIR, "testorg", "testrepo", "newthis", "newthat"); - const data = fs.promises.readFile(path.join(__dirname, "..", "example_reports", "tarpaulin-report.html")); + const reportPath = path.join( + HOST_DIR, + "testorg", + "testrepo", + "newthis", + "newthat" + ); + const data = fs.promises.readFile( + path.join(__dirname, "..", "example_reports", "tarpaulin-report.html") + ); beforeEach(async () => { - await fs.promises.rmdir(report_path).catch(() => { }); + try { + await fs.promises.rmdir(reportPath); + } catch (err) { + // ignore failures for rmdir + } }); describe("POST /v1/:org/:repo/:branch/:commit.html", () => { it("should upload the report and generate a badge", async () => { const mockMeta = mock(); await request(mockMeta) - .post(`/v1/testorg/testrepo/newthis/newthat.html?token=${TOKEN}&format=tarpaulin`) + .post( + `/v1/testorg/testrepo/newthis/newthat.html?token=${TOKEN}&format=tarpaulin` + ) .send(await data) .expect(200); - expect(mockMeta.updateBranch).toBeCalledWith({ organization: "testorg", repository: "testrepo", branch: "newthis", head: "newthat" }); + expect(mockMeta.updateBranch).toBeCalledWith({ + organization: "testorg", + repository: "testrepo", + branch: "newthis", + head: "newthat" + }); expect(mockMeta.updateBranch).toHaveBeenCalledTimes(1); - await fs.promises.access(path.join(report_path, "index.html"), fs.constants.R_OK); - await fs.promises.access(path.join(report_path, "badge.svg"), fs.constants.R_OK); + await fs.promises.access( + path.join(reportPath, "index.html"), + fs.constants.R_OK + ); + await fs.promises.access( + path.join(reportPath, "badge.svg"), + fs.constants.R_OK + ); }); it("should return 401 when token is not correct", async () => { await request() - .post(`/v1/testorg/testrepo/newthis/newthat.html?token=wrong&format=tarpaulin`) + .post( + `/v1/testorg/testrepo/newthis/newthat.html?token=wrong&format=tarpaulin` + ) .send(await data) .expect(401); }); it("should return 406 with an invalid format", async () => { await request() - .post(`/v1/testorg/testrepo/newthis/newthat.html?token=${TOKEN}&format=pepperoni`) + .post( + `/v1/testorg/testrepo/newthis/newthat.html?token=${TOKEN}&format=pepperoni` + ) .send(await data) .expect(406); }); it("should return 400 when request body is not the appropriate format", async () => { await request() - .post(`/v1/testorg/testrepo/newthis/newthat.html?token=${TOKEN}&format=tarpaulin`) + .post( + `/v1/testorg/testrepo/newthis/newthat.html?token=${TOKEN}&format=tarpaulin` + ) .send("This is not a file") .expect(400); }); it("should return 413 when request body is not the appropriate format", async () => { const file = await data; - const big_data = Buffer.concat([file, file]); + const bigData = Buffer.concat([file, file]); await request() - .post(`/v1/testorg/testrepo/newthis/newthat.html?token=${TOKEN}&format=tarpaulin`) - .send(big_data) + .post( + `/v1/testorg/testrepo/newthis/newthat.html?token=${TOKEN}&format=tarpaulin` + ) + .send(bigData) .expect(413); }); it("should return 500 when Metadata does not create branch", async () => { const update = jest.fn(() => new Promise(solv => solv(false))); await request(mock(jest.fn(), update)) - .post(`/v1/testorg/testrepo/newthis/newthat.html?token=${TOKEN}&format=tarpaulin`) + .post( + `/v1/testorg/testrepo/newthis/newthat.html?token=${TOKEN}&format=tarpaulin` + ) .send(await data) .expect(500); }); @@ -231,9 +354,11 @@ describe("Uploads", () => { it("should return 500 when promise chain is rejected", async () => { const update = jest.fn(() => new Promise((_, rej) => rej("fooey 2"))); await request(mock(jest.fn(), update)) - .post(`/v1/testorg/testrepo/newthis/newthat.html?token=${TOKEN}&format=tarpaulin`) + .post( + `/v1/testorg/testrepo/newthis/newthat.html?token=${TOKEN}&format=tarpaulin` + ) .send(await data) .expect(500); }); }); -});
\ No newline at end of file +}); diff --git a/src/routes.ts b/src/routes.ts index 8cb4555..ff3220c 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -5,7 +5,6 @@ import winston from "winston"; import path from "path"; import fs from "fs"; -import processTemplate, { Template } from "./templates"; import formats, { GradientStyle } from "./formats"; import Metadata, { HeadIdentity } from "./metadata"; import { configOrError } from "./util/config"; @@ -15,48 +14,15 @@ import { Messages } from "./errors"; const TOKEN = process.env.TOKEN ?? ""; const UPLOAD_LIMIT = Number(process.env.UPLOAD_LIMIT ?? 4194304); const HOST_DIR = configOrError("HOST_DIR"); -const TARGET_URL = process.env.TARGET_URL ?? "http://localhost:3000"; const logger = winston.createLogger(loggerConfig("HTTP")); export default (metadata: Metadata): Router => { const router = Router(); - const bashTemplate = { - inputFile: path.join(__dirname, "..", "public", "templates", "bash.template"), - outputFile: path.join(HOST_DIR, "bash"), - context: { TARGET_URL } - } as Template; - const indexTemplate = { - inputFile: path.join(__dirname, "..", "public", "templates", "index.html.template"), - outputFile: path.join(HOST_DIR, "index.html"), - context: { TARGET_URL } - } as Template; - - processTemplate(bashTemplate) - .then(template => { - logger.debug("Generated '%s' from template file", template.outputFile); - }) - .then(() => processTemplate(indexTemplate)) - .then(template => { - logger.debug("Generated '%s' from template file", template.outputFile); - }) - .catch(err => { - logger.error("Unable to process template file: %s", err); - - // if the output file exists, then we are fine with continuing without - return fs.promises.access(bashTemplate.outputFile, fs.constants.R_OK); - }) - .then(() => fs.promises.access(indexTemplate.outputFile, fs.constants.R_OK)) - .catch(err => { - logger.error("Cannot proceed: %s", err); - - process.exit(1); - }); - // serve landing page router.get("/", (_, res) => { - res.sendFile(path.join(HOST_DIR, "index.html")) + res.sendFile(path.join(HOST_DIR, "index.html")); }); // serve script for posting coverage report @@ -73,7 +39,8 @@ export default (metadata: Metadata): Router => { res.sendFile(path.join(__dirname, "..", "public", "static", "favicon.ico")); }); router.use( - "/static", express.static(path.join(__dirname, "..", "public", "static")) + "/static", + express.static(path.join(__dirname, "..", "public", "static")) ); // Upload HTML file @@ -86,7 +53,7 @@ export default (metadata: Metadata): Router => { return res.status(401).send(Messages.InvalidToken); } - if (typeof format !== 'string' || !formats.listFormats().includes(format)) { + if (typeof format !== "string" || !formats.listFormats().includes(format)) { return res.status(406).send(Messages.InvalidFormat); } @@ -149,9 +116,12 @@ export default (metadata: Metadata): Router => { result ? res.status(200).send() : res.status(500).send(Messages.UnknownError) - ).catch(err => { - logger.error(err ?? "Unknown error occurred while processing POST request"); - return res.status(500).send(Messages.UnknownError) + ) + .catch(err => { + logger.error( + err ?? "Unknown error occurred while processing POST request" + ); + return res.status(500).send(Messages.UnknownError); }); }); }); @@ -189,7 +159,9 @@ export default (metadata: Metadata): Router => { } }, err => { - logger.error(err ?? "Error occurred while fetching commit for GET request"); + logger.error( + err ?? "Error occurred while fetching commit for GET request" + ); res.status(500).send(Messages.UnknownError); } ); @@ -213,7 +185,9 @@ export default (metadata: Metadata): Router => { } }, err => { - logger.error(err ?? "Error occurred while fetching commit for GET request"); + logger.error( + err ?? "Error occurred while fetching commit for GET request" + ); res.status(500).send(Messages.UnknownError); } ); diff --git a/src/util/config.test.ts b/src/util/config.test.ts index 19d7564..418edb7 100644 --- a/src/util/config.test.ts +++ b/src/util/config.test.ts @@ -1,11 +1,11 @@ +const exit = jest + .spyOn(process, "exit") + .mockImplementation(() => undefined as never); + import { configOrError, handleShutdown } from "./config"; import { MongoClient } from "mongodb"; import { Server } from "http"; -const exit = jest.spyOn(process, "exit").mockImplementation(() => { - throw Error(""); -}); - const CommonMocks = { connect: jest.fn(), isConnected: jest.fn(), @@ -111,7 +111,7 @@ describe("handleShutdown", () => { } // Assert - expect(exit).toHaveBeenCalledWith(1); + expect(exit).toHaveBeenCalledWith(0); }); it("should exit with error with Mongo error", async () => { diff --git a/src/util/config.ts b/src/util/config.ts index 1eddda3..b3a1ead 100644 --- a/src/util/config.ts +++ b/src/util/config.ts @@ -1,8 +1,11 @@ import winston from "winston"; -import { MongoClient } from "mongodb"; +import { MongoClient, MongoError } from "mongodb"; import { Server } from "http"; +import path from "path"; +import fs from "fs"; import loggerConfig from "./logger"; +import processTemplate, { Template } from "../templates"; const logger = winston.createLogger(loggerConfig("ROOT")); @@ -16,6 +19,76 @@ export const configOrError = (varName: string): string => { } }; +export const persistTemplate = async (input: Template): Promise<void> => { + try { + const template = await processTemplate(input); + logger.debug("Generated '%s' from template file", template.outputFile); + } catch (err1) { + try { + await fs.promises.access(input.outputFile, fs.constants.R_OK); + } catch (err2) { + logger.error( + "Error while generating '%s' from template file: %s", + input.outputFile, + err1 + ); + logger.error("Cannot proceed due to error: %s", err2); + + process.exit(1); + } + // if the output file exists, then we are fine with continuing without + logger.warning( + "Could not generate '%s' from template file, but file already exists: %s", + input.outputFile, + err1 + ); + } +}; + +const MONGO_URI = configOrError("MONGO_URI"); +const TARGET_URL = process.env.TARGET_URL ?? "http://localhost:3000"; +const HOST_DIR = configOrError("HOST_DIR"); + +export const handleStartup = async (): Promise<MongoClient> => { + await fs.promises.access(HOST_DIR, fs.constants.R_OK | fs.constants.W_OK); + if (!path.isAbsolute(HOST_DIR)) { + logger.error("HOST_DIR must be an absolute path"); + process.exit(1); + } + + const mongo = await new MongoClient(MONGO_URI, { useUnifiedTopology: true }) + .connect() + .catch((err: MongoError) => { + logger.error(err.message ?? "Unable to connect to database"); + process.exit(1); + }); + + await persistTemplate({ + inputFile: path.join( + __dirname, + "..", + "public", + "templates", + "bash.template" + ), + outputFile: path.join(HOST_DIR, "bash"), + context: { TARGET_URL } + } as Template); + await persistTemplate({ + inputFile: path.join( + __dirname, + "..", + "public", + "templates", + "index.html.template" + ), + outputFile: path.join(HOST_DIR, "index.html"), + context: { TARGET_URL } + } as Template); + + return mongo; +}; + export const handleShutdown = (mongo: MongoClient, server: Server) => ( signal: NodeJS.Signals ): Promise<void> => { |
