aboutsummaryrefslogtreecommitdiff
path: root/src/server/ApiManagers
diff options
context:
space:
mode:
Diffstat (limited to 'src/server/ApiManagers')
-rw-r--r--src/server/ApiManagers/DeleteManager.ts12
-rw-r--r--src/server/ApiManagers/DiagnosticManager.ts30
-rw-r--r--src/server/ApiManagers/DownloadManager.ts11
-rw-r--r--src/server/ApiManagers/GeneralGoogleManager.ts6
-rw-r--r--src/server/ApiManagers/GooglePhotosManager.ts4
-rw-r--r--src/server/ApiManagers/PDFManager.ts74
-rw-r--r--src/server/ApiManagers/SearchManager.ts52
-rw-r--r--src/server/ApiManagers/SessionManager.ts59
-rw-r--r--src/server/ApiManagers/UploadManager.ts8
-rw-r--r--src/server/ApiManagers/UserManager.ts10
-rw-r--r--src/server/ApiManagers/UtilManager.ts18
11 files changed, 185 insertions, 99 deletions
diff --git a/src/server/ApiManagers/DeleteManager.ts b/src/server/ApiManagers/DeleteManager.ts
index 1fdc7cc36..be452c0ff 100644
--- a/src/server/ApiManagers/DeleteManager.ts
+++ b/src/server/ApiManagers/DeleteManager.ts
@@ -1,5 +1,5 @@
import ApiManager, { Registration } from "./ApiManager";
-import { Method, _permission_denied, OnUnauthenticated } from "../RouteManager";
+import { Method, _permission_denied, PublicHandler } from "../RouteManager";
import { WebSocket } from "../Websocket/Websocket";
import { Database } from "../database";
@@ -10,7 +10,7 @@ export default class DeleteManager extends ApiManager {
register({
method: Method.GET,
subscription: "/delete",
- onValidation: async ({ res, isRelease }) => {
+ secureHandler: async ({ res, isRelease }) => {
if (isRelease) {
return _permission_denied(res, deletionPermissionError);
}
@@ -22,7 +22,7 @@ export default class DeleteManager extends ApiManager {
register({
method: Method.GET,
subscription: "/deleteAll",
- onValidation: async ({ res, isRelease }) => {
+ secureHandler: async ({ res, isRelease }) => {
if (isRelease) {
return _permission_denied(res, deletionPermissionError);
}
@@ -31,7 +31,7 @@ export default class DeleteManager extends ApiManager {
}
});
- const hi: OnUnauthenticated = async ({ res, isRelease }) => {
+ const hi: PublicHandler = async ({ res, isRelease }) => {
if (isRelease) {
return _permission_denied(res, deletionPermissionError);
}
@@ -50,7 +50,7 @@ export default class DeleteManager extends ApiManager {
register({
method: Method.GET,
subscription: "/deleteWithAux",
- onValidation: async ({ res, isRelease }) => {
+ secureHandler: async ({ res, isRelease }) => {
if (isRelease) {
return _permission_denied(res, deletionPermissionError);
}
@@ -62,7 +62,7 @@ export default class DeleteManager extends ApiManager {
register({
method: Method.GET,
subscription: "/deleteWithGoogleCredentials",
- onValidation: async ({ res, isRelease }) => {
+ secureHandler: async ({ res, isRelease }) => {
if (isRelease) {
return _permission_denied(res, deletionPermissionError);
}
diff --git a/src/server/ApiManagers/DiagnosticManager.ts b/src/server/ApiManagers/DiagnosticManager.ts
deleted file mode 100644
index 104985481..000000000
--- a/src/server/ApiManagers/DiagnosticManager.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import ApiManager, { Registration } from "./ApiManager";
-import { Method } from "../RouteManager";
-import request = require('request-promise');
-
-export default class DiagnosticManager extends ApiManager {
-
- protected initialize(register: Registration): void {
-
- register({
- method: Method.GET,
- subscription: "/serverHeartbeat",
- onValidation: ({ res }) => res.send(true)
- });
-
- register({
- method: Method.GET,
- subscription: "/solrHeartbeat",
- onValidation: async ({ res }) => {
- try {
- await request("http://localhost:8983");
- res.send({ running: true });
- } catch (e) {
- res.send({ running: false });
- }
- }
- });
-
- }
-
-} \ No newline at end of file
diff --git a/src/server/ApiManagers/DownloadManager.ts b/src/server/ApiManagers/DownloadManager.ts
index 2280739fc..1bb84f374 100644
--- a/src/server/ApiManagers/DownloadManager.ts
+++ b/src/server/ApiManagers/DownloadManager.ts
@@ -7,6 +7,7 @@ import { Database } from "../database";
import * as path from "path";
import { DashUploadUtils, SizeSuffix } from "../DashUploadUtils";
import { publicDirectory } from "..";
+import { serverPathToFile, Directory } from "./UploadManager";
export type Hierarchy = { [id: string]: string | Hierarchy };
export type ZipMutator = (file: Archiver.Archiver) => void | Promise<void>;
@@ -32,7 +33,7 @@ export default class DownloadManager extends ApiManager {
register({
method: Method.GET,
subscription: new RouteSubscriber("imageHierarchyExport").add('docId'),
- onValidation: async ({ req, res }) => {
+ secureHandler: async ({ req, res }) => {
const id = req.params.docId;
const hierarchy: Hierarchy = {};
await buildHierarchyRecursive(id, hierarchy);
@@ -43,7 +44,7 @@ export default class DownloadManager extends ApiManager {
register({
method: Method.GET,
subscription: new RouteSubscriber("downloadId").add("docId"),
- onValidation: async ({ req, res }) => {
+ secureHandler: async ({ req, res }) => {
return BuildAndDispatchZip(res, async zip => {
const { id, docs, files } = await getDocs(req.params.docId);
const docString = JSON.stringify({ id, docs });
@@ -58,7 +59,7 @@ export default class DownloadManager extends ApiManager {
register({
method: Method.GET,
subscription: new RouteSubscriber("serializeDoc").add("docId"),
- onValidation: async ({ req, res }) => {
+ secureHandler: async ({ req, res }) => {
const { docs, files } = await getDocs(req.params.docId);
res.send({ docs, files: Array.from(files) });
}
@@ -245,9 +246,9 @@ async function writeHierarchyRecursive(file: Archiver.Archiver, hierarchy: Hiera
if (typeof result === "string") {
let path: string;
let matches: RegExpExecArray | null;
- if ((matches = /\:1050\/files\/(upload\_[\da-z]{32}.*)/g.exec(result)) !== null) {
+ if ((matches = /\:1050\/files\/images\/(upload\_[\da-z]{32}.*)/g.exec(result)) !== null) {
// image already exists on our server
- path = `${__dirname}/public/files/${matches[1]}`;
+ path = serverPathToFile(Directory.images, matches[1]);
} else {
// the image doesn't already exist on our server (may have been dragged
// and dropped in the browser and thus hosted remotely) so we upload it
diff --git a/src/server/ApiManagers/GeneralGoogleManager.ts b/src/server/ApiManagers/GeneralGoogleManager.ts
index 3617779d5..a5240edbc 100644
--- a/src/server/ApiManagers/GeneralGoogleManager.ts
+++ b/src/server/ApiManagers/GeneralGoogleManager.ts
@@ -19,7 +19,7 @@ export default class GeneralGoogleManager extends ApiManager {
register({
method: Method.GET,
subscription: "/readGoogleAccessToken",
- onValidation: async ({ user, res }) => {
+ secureHandler: async ({ user, res }) => {
const token = await GoogleApiServerUtils.retrieveAccessToken(user.id);
if (!token) {
return res.send(GoogleApiServerUtils.generateAuthenticationUrl());
@@ -31,7 +31,7 @@ export default class GeneralGoogleManager extends ApiManager {
register({
method: Method.POST,
subscription: "/writeGoogleAccessToken",
- onValidation: async ({ user, req, res }) => {
+ secureHandler: async ({ user, req, res }) => {
res.send(await GoogleApiServerUtils.processNewUser(user.id, req.body.authenticationCode));
}
});
@@ -39,7 +39,7 @@ export default class GeneralGoogleManager extends ApiManager {
register({
method: Method.POST,
subscription: new RouteSubscriber("googleDocs").add("sector", "action"),
- onValidation: async ({ req, res, user }) => {
+ secureHandler: async ({ req, res, user }) => {
const sector: GoogleApiServerUtils.Service = req.params.sector as GoogleApiServerUtils.Service;
const action: GoogleApiServerUtils.Action = req.params.action as GoogleApiServerUtils.Action;
const endpoint = await GoogleApiServerUtils.GetEndpoint(GoogleApiServerUtils.Service[sector], user.id);
diff --git a/src/server/ApiManagers/GooglePhotosManager.ts b/src/server/ApiManagers/GooglePhotosManager.ts
index e2539f120..107542ce2 100644
--- a/src/server/ApiManagers/GooglePhotosManager.ts
+++ b/src/server/ApiManagers/GooglePhotosManager.ts
@@ -41,7 +41,7 @@ export default class GooglePhotosManager extends ApiManager {
register({
method: Method.POST,
subscription: "/googlePhotosMediaUpload",
- onValidation: async ({ user, req, res }) => {
+ secureHandler: async ({ user, req, res }) => {
const { media } = req.body;
const token = await GoogleApiServerUtils.retrieveAccessToken(user.id);
if (!token) {
@@ -82,7 +82,7 @@ export default class GooglePhotosManager extends ApiManager {
register({
method: Method.POST,
subscription: "/googlePhotosMediaDownload",
- onValidation: async ({ req, res }) => {
+ secureHandler: async ({ req, res }) => {
const contents: { mediaItems: MediaItem[] } = req.body;
let failed = 0;
if (contents) {
diff --git a/src/server/ApiManagers/PDFManager.ts b/src/server/ApiManagers/PDFManager.ts
index a190ab0cb..0136b758e 100644
--- a/src/server/ApiManagers/PDFManager.ts
+++ b/src/server/ApiManagers/PDFManager.ts
@@ -1,7 +1,7 @@
import ApiManager, { Registration } from "./ApiManager";
import { Method } from "../RouteManager";
import RouteSubscriber from "../RouteSubscriber";
-import { exists, createReadStream, createWriteStream } from "fs";
+import { existsSync, createReadStream, createWriteStream } from "fs";
import * as Pdfjs from 'pdfjs-dist';
import { createCanvas } from "canvas";
const imageSize = require("probe-image-size");
@@ -17,42 +17,37 @@ export default class PDFManager extends ApiManager {
register({
method: Method.GET,
subscription: new RouteSubscriber("thumbnail").add("filename"),
- onValidation: ({ req, res }) => getOrCreateThumbnail(req.params.filename, res)
+ secureHandler: ({ req, res }) => getOrCreateThumbnail(req.params.filename, res)
});
}
}
-function getOrCreateThumbnail(thumbnailName: string, res: express.Response) {
+async function getOrCreateThumbnail(thumbnailName: string, res: express.Response): Promise<void> {
const noExtension = thumbnailName.substring(0, thumbnailName.length - ".png".length);
const pageString = noExtension.split('-')[1];
const pageNumber = parseInt(pageString);
- return new Promise<void>(resolve => {
+ return new Promise<void>(async resolve => {
const path = serverPathToFile(Directory.pdf_thumbnails, thumbnailName);
- exists(path, (exists: boolean) => {
- if (exists) {
- const existingThumbnail = createReadStream(path);
- imageSize(existingThumbnail, (err: any, { width, height }: any) => {
- if (err) {
- console.log(red(`In PDF thumbnail response, unable to determine dimensions of ${thumbnailName}:`));
- console.log(err);
- return;
- }
- res.send({
- path: clientPathToFile(Directory.pdf_thumbnails, thumbnailName),
- width,
- height
- });
- });
- } else {
- const offset = thumbnailName.length - pageString.length - 5;
- const name = thumbnailName.substring(0, offset) + ".pdf";
- const path = serverPathToFile(Directory.pdfs, name);
- CreateThumbnail(path, pageNumber, res);
+ if (existsSync(path)) {
+ const existingThumbnail = createReadStream(path);
+ const { err, viewport } = await new Promise<any>(resolve => {
+ imageSize(existingThumbnail, (err: any, viewport: any) => resolve({ err, viewport }));
+ });
+ if (err) {
+ console.log(red(`In PDF thumbnail response, unable to determine dimensions of ${thumbnailName}:`));
+ console.log(err);
+ return;
}
- resolve();
- });
+ dispatchThumbnail(res, viewport, thumbnailName);
+ } else {
+ const offset = thumbnailName.length - pageString.length - 5;
+ const name = thumbnailName.substring(0, offset) + ".pdf";
+ const path = serverPathToFile(Directory.pdfs, name);
+ await CreateThumbnail(path, pageNumber, res);
+ }
+ resolve();
});
}
@@ -70,19 +65,28 @@ async function CreateThumbnail(file: string, pageNumber: number, res: express.Re
await page.render(renderContext).promise;
const pngStream = canvas.createPNGStream();
const filenames = path.basename(file).split(".");
- const pngFile = serverPathToFile(Directory.pdf_thumbnails, `${filenames[0]}-${pageNumber}.png`);
+ const thumbnailName = `${filenames[0]}-${pageNumber}.png`;
+ const pngFile = serverPathToFile(Directory.pdf_thumbnails, thumbnailName);
const out = createWriteStream(pngFile);
pngStream.pipe(out);
- out.on("finish", () => {
- res.send({
- path: pngFile,
- width: viewport.width,
- height: viewport.height
+ return new Promise<void>((resolve, reject) => {
+ out.on("finish", () => {
+ dispatchThumbnail(res, viewport, thumbnailName);
+ resolve();
+ });
+ out.on("error", error => {
+ console.log(red(`In PDF thumbnail creation, encountered the following error when piping ${pngFile}:`));
+ console.log(error);
+ reject();
});
});
- out.on("error", error => {
- console.log(red(`In PDF thumbnail creation, encountered the following error when piping ${pngFile}:`));
- console.log(error);
+}
+
+function dispatchThumbnail(res: express.Response, { width, height }: Pdfjs.PDFPageViewport, thumbnailName: string) {
+ res.send({
+ path: clientPathToFile(Directory.pdf_thumbnails, thumbnailName),
+ width,
+ height
});
}
diff --git a/src/server/ApiManagers/SearchManager.ts b/src/server/ApiManagers/SearchManager.ts
index 7afecbb18..4ce12f9f3 100644
--- a/src/server/ApiManagers/SearchManager.ts
+++ b/src/server/ApiManagers/SearchManager.ts
@@ -4,15 +4,34 @@ import { Search } from "../Search";
const findInFiles = require('find-in-files');
import * as path from 'path';
import { pathToDirectory, Directory } from "./UploadManager";
+import { red, cyan, yellow } from "colors";
+import RouteSubscriber from "../RouteSubscriber";
+import { exec } from "child_process";
+import { onWindows } from "..";
+import { get } from "request-promise";
-export default class SearchManager extends ApiManager {
+export class SearchManager extends ApiManager {
protected initialize(register: Registration): void {
register({
method: Method.GET,
+ subscription: new RouteSubscriber("solr").add("action"),
+ secureHandler: async ({ req, res }) => {
+ const { action } = req.params;
+ if (["start", "stop"].includes(action)) {
+ const status = req.params.action === "start";
+ const success = await SolrManager.SetRunning(status);
+ console.log(success ? `Successfully ${status ? "started" : "stopped"} Solr!` : `Uh oh! Check the console for the error that occurred while ${status ? "starting" : "stopping"} Solr`);
+ }
+ res.redirect("/home");
+ }
+ });
+
+ register({
+ method: Method.GET,
subscription: "/textsearch",
- onValidation: async ({ req, res }) => {
+ secureHandler: async ({ req, res }) => {
const q = req.query.q;
if (q === undefined) {
res.send([]);
@@ -32,18 +51,43 @@ export default class SearchManager extends ApiManager {
register({
method: Method.GET,
subscription: "/search",
- onValidation: async ({ req, res }) => {
+ secureHandler: async ({ req, res }) => {
const solrQuery: any = {};
["q", "fq", "start", "rows", "hl", "hl.fl"].forEach(key => solrQuery[key] = req.query[key]);
if (solrQuery.q === undefined) {
res.send([]);
return;
}
- const results = await Search.Instance.search(solrQuery);
+ const results = await Search.search(solrQuery);
res.send(results);
}
});
}
+}
+
+export namespace SolrManager {
+
+ const command = onWindows ? "solr.cmd" : "solr";
+
+ export async function SetRunning(status: boolean): Promise<boolean> {
+ const args = status ? "start" : "stop -p 8983";
+ console.log(`solr management: trying to ${args}`);
+ exec(`${command} ${args}`, { cwd: "./solr-8.3.1/bin" }, (error, stdout, stderr) => {
+ if (error) {
+ console.log(red(`solr management error: unable to ${args} server`));
+ console.log(red(error.message));
+ }
+ console.log(cyan(stdout));
+ console.log(yellow(stderr));
+ });
+ try {
+ await get("http://localhost:8983");
+ return true;
+ } catch {
+ return false;
+ }
+ }
+
} \ No newline at end of file
diff --git a/src/server/ApiManagers/SessionManager.ts b/src/server/ApiManagers/SessionManager.ts
new file mode 100644
index 000000000..a99aa05e0
--- /dev/null
+++ b/src/server/ApiManagers/SessionManager.ts
@@ -0,0 +1,59 @@
+import ApiManager, { Registration } from "./ApiManager";
+import { Method, _permission_denied, AuthorizedCore, SecureHandler } from "../RouteManager";
+import RouteSubscriber from "../RouteSubscriber";
+import { sessionAgent } from "..";
+import { DashSessionAgent } from "../DashSession/DashSessionAgent";
+
+const permissionError = "You are not authorized!";
+
+export default class SessionManager extends ApiManager {
+
+ private secureSubscriber = (root: string, ...params: string[]) => new RouteSubscriber(root).add("sessionKey", ...params);
+
+ private authorizedAction = (handler: SecureHandler) => {
+ return (core: AuthorizedCore) => {
+ const { req, res, isRelease } = core;
+ const { sessionKey } = req.params;
+ if (!isRelease) {
+ return res.send("This can be run only on the release server.");
+ }
+ if (sessionKey !== process.env.session_key) {
+ return _permission_denied(res, permissionError);
+ }
+ return handler(core);
+ };
+ }
+
+ protected initialize(register: Registration): void {
+
+ register({
+ method: Method.GET,
+ subscription: this.secureSubscriber("debug", "to?"),
+ secureHandler: this.authorizedAction(async ({ req: { params }, res }) => {
+ const to = params.to || DashSessionAgent.notificationRecipient;
+ const { error } = await sessionAgent.serverWorker.emit("debug", { to });
+ res.send(error ? error.message : `Your request was successful: the server captured and compressed (but did not save) a new back up. It was sent to ${to}.`);
+ })
+ });
+
+ register({
+ method: Method.GET,
+ subscription: this.secureSubscriber("backup"),
+ secureHandler: this.authorizedAction(async ({ res }) => {
+ const { error } = await sessionAgent.serverWorker.emit("backup");
+ res.send(error ? error.message : "Your request was successful: the server successfully created a new back up.");
+ })
+ });
+
+ register({
+ method: Method.GET,
+ subscription: this.secureSubscriber("kill"),
+ secureHandler: this.authorizedAction(({ res }) => {
+ res.send("Your request was successful: the server and its session have been killed.");
+ sessionAgent.killSession("an authorized user has manually ended the server session via the /kill route");
+ })
+ });
+
+ }
+
+} \ No newline at end of file
diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts
index da1f83b75..74f45ae62 100644
--- a/src/server/ApiManagers/UploadManager.ts
+++ b/src/server/ApiManagers/UploadManager.ts
@@ -41,7 +41,7 @@ export default class UploadManager extends ApiManager {
register({
method: Method.POST,
subscription: "/upload",
- onValidation: async ({ req, res }) => {
+ secureHandler: async ({ req, res }) => {
const form = new formidable.IncomingForm();
form.uploadDir = pathToDirectory(Directory.parsed_files);
form.keepExtensions = true;
@@ -62,7 +62,7 @@ export default class UploadManager extends ApiManager {
register({
method: Method.POST,
subscription: "/uploadDoc",
- onValidation: ({ req, res }) => {
+ secureHandler: ({ req, res }) => {
const form = new formidable.IncomingForm();
form.keepExtensions = true;
// let path = req.body.path;
@@ -166,7 +166,7 @@ export default class UploadManager extends ApiManager {
register({
method: Method.POST,
subscription: "/inspectImage",
- onValidation: async ({ req, res }) => {
+ secureHandler: async ({ req, res }) => {
const { source } = req.body;
if (typeof source === "string") {
const { serverAccessPaths } = await DashUploadUtils.UploadImage(source);
@@ -179,7 +179,7 @@ export default class UploadManager extends ApiManager {
register({
method: Method.POST,
subscription: "/uploadURI",
- onValidation: ({ req, res }) => {
+ secureHandler: ({ req, res }) => {
const uri = req.body.uri;
const filename = req.body.name;
if (!uri || !filename) {
diff --git a/src/server/ApiManagers/UserManager.ts b/src/server/ApiManagers/UserManager.ts
index 4556e01ea..36d48e366 100644
--- a/src/server/ApiManagers/UserManager.ts
+++ b/src/server/ApiManagers/UserManager.ts
@@ -18,7 +18,7 @@ export default class UserManager extends ApiManager {
register({
method: Method.GET,
subscription: "/getUsers",
- onValidation: async ({ res }) => {
+ secureHandler: async ({ res }) => {
const cursor = await Database.Instance.query({}, { email: 1, userDocumentId: 1 }, "users");
const results = await cursor.toArray();
res.send(results.map(user => ({ email: user.email, userDocumentId: user.userDocumentId })));
@@ -28,14 +28,14 @@ export default class UserManager extends ApiManager {
register({
method: Method.GET,
subscription: "/getUserDocumentId",
- onValidation: ({ res, user }) => res.send(user.userDocumentId)
+ secureHandler: ({ res, user }) => res.send(user.userDocumentId)
});
register({
method: Method.GET,
subscription: "/getCurrentUser",
- onValidation: ({ res, user }) => res.send(JSON.stringify(user)),
- onUnauthenticated: ({ res }) => res.send(JSON.stringify({ id: "__guest__", email: "" }))
+ secureHandler: ({ res, user }) => res.send(JSON.stringify(user)),
+ publicHandler: ({ res }) => res.send(JSON.stringify({ id: "__guest__", email: "" }))
});
register({
@@ -94,7 +94,7 @@ export default class UserManager extends ApiManager {
register({
method: Method.GET,
subscription: "/activity",
- onValidation: ({ res }) => {
+ secureHandler: ({ res }) => {
const now = Date.now();
const activeTimes: ActivityUnit[] = [];
diff --git a/src/server/ApiManagers/UtilManager.ts b/src/server/ApiManagers/UtilManager.ts
index 601a7d0d0..a0d0d0f4b 100644
--- a/src/server/ApiManagers/UtilManager.ts
+++ b/src/server/ApiManagers/UtilManager.ts
@@ -3,6 +3,7 @@ import { Method } from "../RouteManager";
import { exec } from 'child_process';
import { command_line } from "../ActionUtilities";
import RouteSubscriber from "../RouteSubscriber";
+import { red } from "colors";
export default class UtilManager extends ApiManager {
@@ -11,13 +12,20 @@ export default class UtilManager extends ApiManager {
register({
method: Method.GET,
subscription: new RouteSubscriber("environment").add("key"),
- onValidation: ({ req, res }) => res.send(process.env[req.params.key])
+ secureHandler: ({ req, res }) => {
+ const { key } = req.params;
+ const value = process.env[key];
+ if (!value) {
+ console.log(red(`process.env.${key} is not defined.`));
+ }
+ return res.send(value);
+ }
});
register({
method: Method.GET,
subscription: "/pull",
- onValidation: async ({ res }) => {
+ secureHandler: async ({ res }) => {
return new Promise<void>(resolve => {
exec('"C:\\Program Files\\Git\\git-bash.exe" -c "git pull"', err => {
if (err) {
@@ -34,8 +42,8 @@ export default class UtilManager extends ApiManager {
register({
method: Method.GET,
subscription: "/buxton",
- onValidation: async ({ res }) => {
- const cwd = '../scraping/buxton';
+ secureHandler: async ({ res }) => {
+ const cwd = './src/scraping/buxton';
const onResolved = (stdout: string) => { console.log(stdout); res.redirect("/"); };
const onRejected = (err: any) => { console.error(err.message); res.send(err); };
@@ -48,7 +56,7 @@ export default class UtilManager extends ApiManager {
register({
method: Method.GET,
subscription: "/version",
- onValidation: ({ res }) => {
+ secureHandler: ({ res }) => {
return new Promise<void>(resolve => {
exec('"C:\\Program Files\\Git\\bin\\git.exe" rev-parse HEAD', (err, stdout) => {
if (err) {