Index: frontend/node_modules/tsconfig-paths/src/__tests__/config-loader.test.ts
===================================================================
--- frontend/node_modules/tsconfig-paths/src/__tests__/config-loader.test.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/tsconfig-paths/src/__tests__/config-loader.test.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,100 @@
+import {
+  configLoader,
+  loadConfig,
+  ConfigLoaderFailResult,
+  ConfigLoaderSuccessResult,
+} from "../config-loader";
+import { join } from "path";
+
+describe("config-loader", (): void => {
+  it("should use explicitParams when set", () => {
+    const result = configLoader({
+      explicitParams: {
+        baseUrl: "/foo/bar",
+        paths: {
+          asd: ["asd"],
+        },
+      },
+      cwd: "/baz",
+    });
+
+    const successResult = result as ConfigLoaderSuccessResult;
+    // assert.equal(successResult.resultType, "success");
+    // assert.equal(successResult.absoluteBaseUrl, "/foo/bar");
+    // assert.equal(successResult.paths["asd"][0], "asd");
+    expect(successResult.resultType).toBe("success");
+    expect(successResult.absoluteBaseUrl).toBe("/foo/bar");
+    expect(successResult.paths["asd"][0]).toBe("asd");
+  });
+
+  it("should use explicitParams when set and add cwd when path is relative", () => {
+    const result = configLoader({
+      explicitParams: {
+        baseUrl: "bar/",
+        paths: {
+          asd: ["asd"],
+        },
+      },
+      cwd: "/baz",
+    });
+
+    const successResult = result as ConfigLoaderSuccessResult;
+    // assert.equal(successResult.resultType, "success");
+    // assert.equal(successResult.absoluteBaseUrl, join("/baz", "bar/"));
+    expect(successResult.resultType).toBe("success");
+    expect(successResult.absoluteBaseUrl).toBe(join("/baz", "bar/"));
+  });
+
+  it("should fallback to tsConfigLoader when explicitParams is not set", () => {
+    const result = configLoader({
+      explicitParams: undefined,
+      cwd: "/baz",
+      // tslint:disable-next-line:no-any
+      tsConfigLoader: (_: any) => ({
+        tsConfigPath: "/baz/tsconfig.json",
+        baseUrl: "./src",
+        paths: {},
+      }),
+    });
+
+    const successResult = result as ConfigLoaderSuccessResult;
+    // assert.equal(successResult.resultType, "success");
+    // assert.equal(successResult.absoluteBaseUrl, join("/baz", "src"));
+    expect(successResult.resultType).toBe("success");
+    expect(successResult.absoluteBaseUrl).toBe(join("/baz", "src"));
+  });
+
+  it("should show an error message when baseUrl is missing", () => {
+    const result = configLoader({
+      explicitParams: undefined,
+      cwd: "/baz",
+      // tslint:disable-next-line:no-any
+      tsConfigLoader: (_: any) => ({
+        tsConfigPath: "/baz/tsconfig.json",
+        baseUrl: undefined,
+        paths: {},
+      }),
+    });
+
+    const failResult = result as ConfigLoaderFailResult;
+    // assert.equal(failResult.resultType, "failed");
+    // assert.isTrue(failResult.message.indexOf("baseUrl") > -1);
+    expect(failResult.resultType).toBe("failed");
+    expect(failResult.message.indexOf("baseUrl") > -1).toBeTruthy();
+  });
+
+  it("should presume cwd to be a tsconfig file when loadConfig is called with absolute path to tsconfig.json", () => {
+    // using tsconfig-named.json to ensure that future changes to fix
+    // https://github.com/dividab/tsconfig-paths/issues/31
+    // do not pass this test case just because of a directory walk looking
+    // for tsconfig.json
+    const configFile = join(__dirname, "tsconfig-named.json");
+    const result = loadConfig(configFile);
+
+    const successResult = result as ConfigLoaderSuccessResult;
+    // assert.equal(successResult.resultType, "success");
+    // assert.equal(successResult.configFileAbsolutePath, configFile);
+    expect(successResult.resultType).toBe("success");
+    expect(successResult.configFileAbsolutePath).toBe(configFile);
+  });
+});
Index: frontend/node_modules/tsconfig-paths/src/__tests__/data/match-path-data.ts
===================================================================
--- frontend/node_modules/tsconfig-paths/src/__tests__/data/match-path-data.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/tsconfig-paths/src/__tests__/data/match-path-data.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,219 @@
+import { join, dirname } from "path";
+import { removeExtension } from "../../filesystem";
+
+export interface OneTest {
+  readonly name: string;
+  readonly only?: boolean;
+  readonly skip?: boolean;
+  readonly absoluteBaseUrl: string;
+  readonly paths: { [key: string]: Array<string> };
+  readonly mainFields?: string[];
+  readonly addMatchAll?: boolean;
+  readonly existingFiles: ReadonlyArray<string>;
+  readonly requestedModule: string;
+  readonly extensions: ReadonlyArray<string>;
+  readonly packageJson?: {};
+  readonly expectedPath: string | undefined;
+}
+
+const defaultExtensionsWhenRunningInTsNode = [
+  ".js",
+  ".json",
+  ".node",
+  ".ts",
+  ".tsx",
+];
+
+export const tests: ReadonlyArray<OneTest> = [
+  {
+    name: "should locate path that matches with star and exists",
+    absoluteBaseUrl: "/root/",
+    paths: {
+      "lib/*": ["location/*"],
+    },
+    existingFiles: [join("/root", "location", "mylib", "index.ts")],
+    requestedModule: "lib/mylib",
+    expectedPath: dirname(join("/root", "location", "mylib", "index.ts")),
+    extensions: defaultExtensionsWhenRunningInTsNode,
+  },
+  {
+    name: "should resolve to correct path when many are specified",
+    absoluteBaseUrl: "/root/",
+    paths: {
+      "lib/*": ["foo1/*", "foo2/*", "location/*", "foo3/*"],
+    },
+    existingFiles: [join("/root", "location", "mylib", "index.ts")],
+    requestedModule: "lib/mylib",
+    extensions: [".ts"],
+    expectedPath: dirname(join("/root", "location", "mylib", "index.ts")),
+  },
+  {
+    name:
+      "should locate path that matches with star and prioritize pattern with longest prefix",
+    absoluteBaseUrl: "/root/",
+    paths: {
+      "*": ["location/*"],
+      "lib/*": ["location/*"],
+    },
+    existingFiles: [
+      join("/root", "location", "lib", "mylib", "index.ts"),
+      join("/root", "location", "mylib", "index.ts"),
+    ],
+    requestedModule: "lib/mylib",
+    expectedPath: dirname(join("/root", "location", "mylib", "index.ts")),
+    extensions: defaultExtensionsWhenRunningInTsNode,
+  },
+  {
+    name: "should locate path that matches with star and exists with extension",
+    absoluteBaseUrl: "/root/",
+    paths: { "lib/*": ["location/*"] },
+    existingFiles: [join("/root", "location", "mylib.myext")],
+    requestedModule: "lib/mylib",
+    extensions: [".js", ".myext"],
+    expectedPath: removeExtension(join("/root", "location", "mylib.myext")),
+  },
+  {
+    name: "should resolve request with extension specified",
+    absoluteBaseUrl: "/root/",
+    paths: { "lib/*": ["location/*"] },
+    existingFiles: [join("/root", "location", "test.jpg")],
+    requestedModule: "lib/test.jpg",
+    expectedPath: join("/root", "location", "test.jpg"),
+    extensions: defaultExtensionsWhenRunningInTsNode,
+  },
+  {
+    name: "should locate path that matches without star and exists",
+    absoluteBaseUrl: "/root/",
+    paths: {
+      "lib/foo": ["location/foo"],
+    },
+    existingFiles: [join("/root", "location", "foo.ts")],
+    requestedModule: "lib/foo",
+    expectedPath: removeExtension(join("/root", "location", "foo.ts")),
+    extensions: defaultExtensionsWhenRunningInTsNode,
+  },
+  {
+    name: "should resolve to parent folder when filename is in subfolder",
+    absoluteBaseUrl: "/root/",
+    paths: { "lib/*": ["location/*"] },
+    existingFiles: [join("/root", "location", "mylib", "index.ts")],
+    requestedModule: "lib/mylib",
+    expectedPath: dirname(join("/root", "location", "mylib", "index.ts")),
+    extensions: defaultExtensionsWhenRunningInTsNode,
+  },
+  {
+    name: "should resolve from main field in package.json",
+    absoluteBaseUrl: "/root/",
+    paths: { "lib/*": ["location/*"] },
+    existingFiles: [join("/root", "location", "mylib", "kalle.ts")],
+    packageJson: { main: "./kalle.ts" },
+    requestedModule: "lib/mylib",
+    expectedPath: join("/root", "location", "mylib", "kalle.ts"),
+    extensions: defaultExtensionsWhenRunningInTsNode,
+  },
+  {
+    name: "should resolve from main field in package.json (js)",
+    absoluteBaseUrl: "/root",
+    paths: { "lib/*": ["location/*"] },
+    existingFiles: [join("/root", "location", "mylib.js", "kalle.js")],
+    packageJson: { main: "./kalle.js" },
+    requestedModule: "lib/mylib.js",
+    extensions: [".ts", ".js"],
+    expectedPath: join("/root", "location", "mylib.js", "kalle.js"),
+  },
+  {
+    name: "should resolve from list of fields by priority in package.json",
+    absoluteBaseUrl: "/root/",
+    paths: { "lib/*": ["location/*"] },
+    mainFields: ["missing", "browser", "main"],
+    packageJson: { main: "./main.js", browser: "./browser.js" },
+    existingFiles: [
+      join("/root", "location", "mylibjs", "main.js"), // mainFilePath
+      join("/root", "location", "mylibjs", "browser.js"), // browserFilePath
+    ],
+    extensions: [".ts", ".js"],
+    requestedModule: "lib/mylibjs",
+    expectedPath: join("/root", "location", "mylibjs", "browser.js"),
+  },
+  {
+    name: "should ignore field mappings to missing files in package.json",
+    absoluteBaseUrl: "/root/",
+    paths: { "lib/*": ["location/*"] },
+    mainFields: ["browser", "main"],
+    existingFiles: [join("/root", "location", "mylibjs", "kalle.js")],
+    requestedModule: "lib/mylibjs",
+    packageJson: {
+      main: "./kalle.js",
+      browser: "./nope.js",
+    },
+    extensions: [".ts", ".js"],
+    expectedPath: join("/root", "location", "mylibjs", "kalle.js"),
+  },
+  {
+    name: "should ignore advanced field mappings in package.json",
+    absoluteBaseUrl: "/root/",
+    paths: { "lib/*": ["location/*"] },
+    existingFiles: [
+      join("/root", "location", "mylibjs", "kalle.js"),
+      join("/root", "location", "mylibjs", "browser.js"),
+    ],
+    requestedModule: "lib/mylibjs",
+    packageJson: {
+      main: "./kalle.js",
+      browser: { mylibjs: "./browser.js", "./kalle.js": "./browser.js" },
+    },
+    extensions: [".ts", ".js"],
+    expectedPath: join("/root", "location", "mylibjs", "kalle.js"),
+  },
+  {
+    name: "should resolve to with the help of baseUrl when not explicitly set",
+    absoluteBaseUrl: "/root/",
+    paths: {},
+    existingFiles: [join("/root", "mylib", "index.ts")],
+    requestedModule: "mylib",
+    expectedPath: dirname(join("/root", "mylib", "index.ts")),
+    extensions: defaultExtensionsWhenRunningInTsNode,
+  },
+  {
+    name: "should not resolve with the help of baseUrl when asked not to",
+    absoluteBaseUrl: "/root/",
+    paths: {},
+    addMatchAll: false,
+    existingFiles: [join("/root", "mylib", "index.ts")],
+    requestedModule: "mylib",
+    expectedPath: undefined,
+    extensions: defaultExtensionsWhenRunningInTsNode,
+  },
+  {
+    name: "should not locate path that does not match",
+    absoluteBaseUrl: "/root/",
+    paths: { "lib/*": ["location/*"] },
+    existingFiles: [join("root", "location", "mylib")],
+    requestedModule: "mylib",
+    expectedPath: undefined,
+    extensions: defaultExtensionsWhenRunningInTsNode,
+  },
+  {
+    name: "should not resolve typings file (index.d.ts)",
+    absoluteBaseUrl: "/root/",
+    paths: {
+      "lib/*": ["location/*"],
+    },
+    existingFiles: [join("/root", "location", "mylib", "index.d.ts")],
+    requestedModule: "lib/mylib",
+    expectedPath: undefined,
+    extensions: defaultExtensionsWhenRunningInTsNode,
+  },
+  {
+    name: "should resolve main file with cjs file extension",
+    absoluteBaseUrl: "/root/",
+    paths: {},
+    existingFiles: [join("/root", "mylib", "index.cjs")],
+    packageJson: {
+      main: "./index.cjs",
+    },
+    requestedModule: "mylib",
+    expectedPath: join("/root", "mylib", "index.cjs"),
+    extensions: defaultExtensionsWhenRunningInTsNode,
+  },
+];
Index: frontend/node_modules/tsconfig-paths/src/__tests__/filesystem.test.ts
===================================================================
--- frontend/node_modules/tsconfig-paths/src/__tests__/filesystem.test.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/tsconfig-paths/src/__tests__/filesystem.test.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,65 @@
+import * as Filesystem from "../filesystem";
+import * as path from "path";
+
+describe("filesystem", () => {
+  const fileThatExists = path.join(__dirname, "../../package.json");
+  const fileThatNotExists = path.join(__dirname, "../../package2.json");
+
+  it("should find file that exists, sync", () => {
+    const result = Filesystem.fileExistsSync(fileThatExists);
+    // assert.equal(result, true);
+    expect(result).toBe(true);
+  });
+
+  it("should not find file that not exists, sync", () => {
+    const result = Filesystem.fileExistsSync(fileThatNotExists);
+    // assert.equal(result, false);
+    expect(result).toBe(false);
+  });
+
+  it("should find file that exists, async", (done) => {
+    Filesystem.fileExistsAsync(fileThatExists, (_err, result) => {
+      try {
+        // assert.equal(result, true);
+        expect(result).toBe(true);
+        done();
+      } catch (error) {
+        done(error);
+      }
+    });
+  });
+
+  it("should not find file that not exists, async", (done) => {
+    Filesystem.fileExistsAsync(fileThatNotExists, (_err, result) => {
+      try {
+        // assert.equal(result, false);
+        expect(result).toBe(false);
+        done();
+      } catch (error) {
+        done(error);
+      }
+    });
+  });
+
+  it("should load json, sync", () => {
+    const result = Filesystem.readJsonFromDiskSync(fileThatExists);
+    // assert.isOk(result);
+    expect(result);
+    // assert.equal(result.main, "lib/index.js");
+    expect(result.main).toBe("lib/index.js");
+  });
+
+  it("should load json, async", (done) => {
+    Filesystem.readJsonFromDiskAsync(fileThatExists, (_err, result) => {
+      try {
+        // assert.isOk(result); // Asserts that object is truthy.
+        expect(result).toBeTruthy();
+        // assert.equal(result.main, "lib/index.js");
+        expect(result.main).toBe("lib/index.js");
+        done();
+      } catch (error) {
+        done(error);
+      }
+    });
+  });
+});
Index: frontend/node_modules/tsconfig-paths/src/__tests__/mapping-entry.test.ts
===================================================================
--- frontend/node_modules/tsconfig-paths/src/__tests__/mapping-entry.test.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/tsconfig-paths/src/__tests__/mapping-entry.test.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,70 @@
+import { getAbsoluteMappingEntries } from "../mapping-entry";
+import { join } from "path";
+
+describe("mapping-entry", () => {
+  it("should change to absolute paths and sort in longest prefix order", () => {
+    const result = getAbsoluteMappingEntries(
+      "/absolute/base/url",
+      {
+        "*": ["/foo1", "/foo2"],
+        "longest/pre/fix/*": ["/foo2/bar"],
+        "pre/fix/*": ["/foo3"],
+      },
+      true
+    );
+    // assert.deepEqual(result, [
+    //   {
+    //     pattern: "longest/pre/fix/*",
+    //     paths: [join("/absolute", "base", "url", "foo2", "bar")],
+    //   },
+    //   {
+    //     pattern: "pre/fix/*",
+    //     paths: [join("/absolute", "base", "url", "foo3")],
+    //   },
+    //   {
+    //     pattern: "*",
+    //     paths: [
+    //       join("/absolute", "base", "url", "foo1"),
+    //       join("/absolute", "base", "url", "foo2"),
+    //     ],
+    //   },
+    // ]);
+    expect(result).toEqual([
+      {
+        pattern: "longest/pre/fix/*",
+        paths: [join("/absolute", "base", "url", "foo2", "bar")],
+      },
+      {
+        pattern: "pre/fix/*",
+        paths: [join("/absolute", "base", "url", "foo3")],
+      },
+      {
+        pattern: "*",
+        paths: [
+          join("/absolute", "base", "url", "foo1"),
+          join("/absolute", "base", "url", "foo2"),
+        ],
+      },
+    ]);
+  });
+
+  it("should should add a match-all pattern when requested", () => {
+    let result = getAbsoluteMappingEntries("/absolute/base/url", {}, true);
+    // assert.deepEqual(result, [
+    //   {
+    //     pattern: "*",
+    //     paths: [join("/absolute", "base", "url", "*")],
+    //   },
+    // ]);
+    expect(result).toEqual([
+      {
+        pattern: "*",
+        paths: [join("/absolute", "base", "url", "*")],
+      },
+    ]);
+
+    result = getAbsoluteMappingEntries("/absolute/base/url", {}, false);
+    // assert.deepEqual(result, []);
+    expect(result).toEqual([]);
+  });
+});
Index: frontend/node_modules/tsconfig-paths/src/__tests__/match-path-async.test.ts
===================================================================
--- frontend/node_modules/tsconfig-paths/src/__tests__/match-path-async.test.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/tsconfig-paths/src/__tests__/match-path-async.test.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,27 @@
+import { createMatchPathAsync } from "../match-path-async";
+import * as Tests from "./data/match-path-data";
+
+describe("match-path-async", () => {
+  Tests.tests.forEach((t) =>
+    it(t.name, (done) => {
+      const matchPath = createMatchPathAsync(
+        t.absoluteBaseUrl,
+        t.paths,
+        t.mainFields,
+        t.addMatchAll
+      );
+      matchPath(
+        t.requestedModule,
+        (_path, callback) => callback(undefined, t.packageJson),
+        (path, callback) =>
+          callback(undefined, t.existingFiles.indexOf(path) !== -1),
+        t.extensions,
+        (_err, result) => {
+          // assert.equal(result, t.expectedPath);
+          expect(result).toBe(t.expectedPath);
+          done();
+        }
+      );
+    })
+  );
+});
Index: frontend/node_modules/tsconfig-paths/src/__tests__/match-path-sync.test.ts
===================================================================
--- frontend/node_modules/tsconfig-paths/src/__tests__/match-path-sync.test.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/tsconfig-paths/src/__tests__/match-path-sync.test.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,23 @@
+import { createMatchPath } from "../match-path-sync";
+import * as Tests from "./data/match-path-data";
+
+describe("match-path-sync", () => {
+  Tests.tests.forEach((t) =>
+    it(t.name, () => {
+      const matchPath = createMatchPath(
+        t.absoluteBaseUrl,
+        t.paths,
+        t.mainFields,
+        t.addMatchAll
+      );
+      const result = matchPath(
+        t.requestedModule,
+        (_: string) => t.packageJson,
+        (name: string) => t.existingFiles.indexOf(name) !== -1,
+        t.extensions
+      );
+      // assert.equal(result, t.expectedPath);
+      expect(result).toBe(t.expectedPath);
+    })
+  );
+});
Index: frontend/node_modules/tsconfig-paths/src/__tests__/try-path.test.ts
===================================================================
--- frontend/node_modules/tsconfig-paths/src/__tests__/try-path.test.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/tsconfig-paths/src/__tests__/try-path.test.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,193 @@
+import { getPathsToTry } from "../try-path";
+import { join } from "path";
+
+describe("mapping-entry", () => {
+  const abosolutePathMappings = [
+    {
+      pattern: "longest/pre/fix/*",
+      paths: [join("/absolute", "base", "url", "foo2", "bar")],
+    },
+    { pattern: "pre/fix/*", paths: [join("/absolute", "base", "url", "foo3")] },
+    { pattern: "*", paths: [join("/absolute", "base", "url", "foo1")] },
+  ];
+  const abosolutePathMappingsStarstWithSlash = [
+    {
+      pattern: "/opt/*",
+      paths: [join("/absolute", "src", "aws-layer")],
+    },
+    {
+      pattern: "*",
+      paths: [join("/absolute", "src")],
+    },
+  ];
+  it("should return no paths for relative requested module", () => {
+    const result = getPathsToTry(
+      [".ts", "tsx"],
+      abosolutePathMappings,
+      "./requested-module"
+    );
+    // assert.deepEqual(result, undefined);
+    expect(result).toBeUndefined();
+  });
+
+  it("should return no paths if no pattern match the requested module", () => {
+    const result = getPathsToTry(
+      [".ts", "tsx"],
+      [
+        {
+          pattern: "longest/pre/fix/*",
+          paths: [join("/absolute", "base", "url", "foo2", "bar")],
+        },
+        {
+          pattern: "pre/fix/*",
+          paths: [join("/absolute", "base", "url", "foo3")],
+        },
+      ],
+      "requested-module"
+    );
+    expect(result).toBeUndefined();
+  });
+
+  it("should get all paths that matches requested module", () => {
+    const result = getPathsToTry(
+      [".ts", ".tsx"],
+      abosolutePathMappings,
+      "longest/pre/fix/requested-module"
+    );
+    // assert.deepEqual(result, [
+    //   // "longest/pre/fix/*"
+    //   { type: "file", path: join("/absolute", "base", "url", "foo2", "bar") },
+    //   {
+    //     type: "extension",
+    //     path: join("/absolute", "base", "url", "foo2", "bar.ts"),
+    //   },
+    //   {
+    //     type: "extension",
+    //     path: join("/absolute", "base", "url", "foo2", "bar.tsx"),
+    //   },
+    //   {
+    //     type: "package",
+    //     path: join("/absolute", "base", "url", "foo2", "bar", "package.json"),
+    //   },
+    //   {
+    //     type: "index",
+    //     path: join("/absolute", "base", "url", "foo2", "bar", "index.ts"),
+    //   },
+    //   {
+    //     type: "index",
+    //     path: join("/absolute", "base", "url", "foo2", "bar", "index.tsx"),
+    //   },
+    //   // "*"
+    //   { type: "file", path: join("/absolute", "base", "url", "foo1") },
+    //   { type: "extension", path: join("/absolute", "base", "url", "foo1.ts") },
+    //   { type: "extension", path: join("/absolute", "base", "url", "foo1.tsx") },
+    //   {
+    //     type: "package",
+    //     path: join("/absolute", "base", "url", "foo1", "package.json"),
+    //   },
+    //   {
+    //     type: "index",
+    //     path: join("/absolute", "base", "url", "foo1", "index.ts"),
+    //   },
+    //   {
+    //     type: "index",
+    //     path: join("/absolute", "base", "url", "foo1", "index.tsx"),
+    //   },
+    // ]);
+    expect(result).toEqual([
+      // "longest/pre/fix/*"
+      { type: "file", path: join("/absolute", "base", "url", "foo2", "bar") },
+      {
+        type: "extension",
+        path: join("/absolute", "base", "url", "foo2", "bar.ts"),
+      },
+      {
+        type: "extension",
+        path: join("/absolute", "base", "url", "foo2", "bar.tsx"),
+      },
+      {
+        type: "package",
+        path: join("/absolute", "base", "url", "foo2", "bar", "package.json"),
+      },
+      {
+        type: "index",
+        path: join("/absolute", "base", "url", "foo2", "bar", "index.ts"),
+      },
+      {
+        type: "index",
+        path: join("/absolute", "base", "url", "foo2", "bar", "index.tsx"),
+      },
+      // "*"
+      { type: "file", path: join("/absolute", "base", "url", "foo1") },
+      { type: "extension", path: join("/absolute", "base", "url", "foo1.ts") },
+      { type: "extension", path: join("/absolute", "base", "url", "foo1.tsx") },
+      {
+        type: "package",
+        path: join("/absolute", "base", "url", "foo1", "package.json"),
+      },
+      {
+        type: "index",
+        path: join("/absolute", "base", "url", "foo1", "index.ts"),
+      },
+      {
+        type: "index",
+        path: join("/absolute", "base", "url", "foo1", "index.tsx"),
+      },
+    ]);
+  });
+
+  it("should resolve paths starting with a slash", () => {
+    const result = getPathsToTry(
+      [".ts"],
+      abosolutePathMappingsStarstWithSlash,
+      "/opt/utils"
+    );
+    expect(result).toEqual([
+      // "opt/*"
+      {
+        path: join("/absolute", "src", "aws-layer"),
+        type: "file",
+      },
+      {
+        path: join("/absolute", "src", "aws-layer.ts"),
+        type: "extension",
+      },
+      {
+        path: join("/absolute", "src", "aws-layer", "package.json"),
+        type: "package",
+      },
+      {
+        path: join("/absolute", "src", "aws-layer", "index.ts"),
+        type: "index",
+      },
+      // "*"
+      {
+        path: join("/absolute", "src"),
+        type: "file",
+      },
+      {
+        path: join("/absolute", "src.ts"),
+        type: "extension",
+      },
+      {
+        path: join("/absolute", "src", "package.json"),
+        type: "package",
+      },
+      {
+        path: join("/absolute", "src", "index.ts"),
+        type: "index",
+      },
+    ]);
+  });
+});
+
+// describe("match-star", () => {
+//   it("should match star in last position", () => {
+//     const result = matchStar("lib/*", "lib/mylib");
+//     assert.equal(result, "mylib");
+//   });
+//   it("should match star in first position", () => {
+//     const result = matchStar("*/lib", "mylib/lib");
+//     assert.equal(result, "mylib");
+//   });
+// });
Index: frontend/node_modules/tsconfig-paths/src/__tests__/tsconfig-loader.test.ts
===================================================================
--- frontend/node_modules/tsconfig-paths/src/__tests__/tsconfig-loader.test.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/tsconfig-paths/src/__tests__/tsconfig-loader.test.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,416 @@
+import {
+  loadTsconfig,
+  tsConfigLoader,
+  walkForTsConfig,
+} from "../tsconfig-loader";
+import { join } from "path";
+
+describe("tsconfig-loader", () => {
+  it("should find tsconfig in cwd", () => {
+    const result = tsConfigLoader({
+      cwd: "/foo/bar",
+      getEnv: (_: string) => undefined,
+      loadSync: (cwd: string) => {
+        return {
+          tsConfigPath: `${cwd}/tsconfig.json`,
+          baseUrl: "./",
+          paths: {},
+        };
+      },
+    });
+
+    // assert.equal(result.tsConfigPath, "/foo/bar/tsconfig.json");
+    expect(result.tsConfigPath).toBe("/foo/bar/tsconfig.json");
+  });
+
+  it("should return loaderResult.tsConfigPath as undefined when not found", () => {
+    const result = tsConfigLoader({
+      cwd: "/foo/bar",
+      getEnv: (_: string) => undefined,
+      loadSync: (_: string) => {
+        return {
+          tsConfigPath: undefined,
+          baseUrl: "./",
+          paths: {},
+        };
+      },
+    });
+
+    // assert.isUndefined(result.tsConfigPath);
+    expect(result.tsConfigPath).toBeUndefined();
+  });
+
+  it("should use TS_NODE_PROJECT env if exists", () => {
+    const result = tsConfigLoader({
+      cwd: "/foo/bar",
+      getEnv: (key: string) =>
+        key === "TS_NODE_PROJECT" ? "/foo/baz" : undefined,
+      loadSync: (cwd: string, fileName: string) => {
+        if (cwd === "/foo/bar" && fileName === "/foo/baz") {
+          return {
+            tsConfigPath: "/foo/baz/tsconfig.json",
+            baseUrl: "./",
+            paths: {},
+          };
+        }
+
+        return {
+          tsConfigPath: undefined,
+          baseUrl: "./",
+          paths: {},
+        };
+      },
+    });
+
+    // assert.equal(result.tsConfigPath, "/foo/baz/tsconfig.json");
+    expect(result.tsConfigPath).toBe("/foo/baz/tsconfig.json");
+  });
+
+  it("should use TS_NODE_BASEURL env if exists", () => {
+    const result = tsConfigLoader({
+      cwd: "/foo/bar",
+      getEnv: (key: string) =>
+        key === "TS_NODE_BASEURL" ? "SOME_BASEURL" : undefined,
+      loadSync: (_0: string, _1: string, baseUrl: string) => {
+        return {
+          tsConfigPath: undefined,
+          baseUrl,
+          paths: {},
+        };
+      },
+    });
+
+    // assert.equal(result.baseUrl, "SOME_BASEURL");
+    expect(result.baseUrl).toBe("SOME_BASEURL");
+  });
+
+  it("should not use TS_NODE_BASEURL env if it does not exist", () => {
+    const result = tsConfigLoader({
+      cwd: "/foo/bar",
+      getEnv: (_: string) => {
+        return undefined;
+      },
+      loadSync: (_0: string, _1: string, baseUrl: string) => {
+        return {
+          tsConfigPath: undefined,
+          baseUrl,
+          paths: {},
+        };
+      },
+    });
+
+    // assert.equal(result.baseUrl, undefined);
+    expect(result.baseUrl).toBeUndefined();
+  });
+});
+
+describe("walkForTsConfig", () => {
+  it("should find tsconfig in starting directory", () => {
+    const pathToTsconfig = join("/root", "dir1", "tsconfig.json");
+    const res = walkForTsConfig(
+      join("/root", "dir1"),
+      (path) => path === pathToTsconfig
+    );
+    // assert.equal(res, pathToTsconfig);
+    expect(res).toBe(pathToTsconfig);
+  });
+
+  it("should find tsconfig in parent directory", () => {
+    const pathToTsconfig = join("/root", "tsconfig.json");
+    const res = walkForTsConfig(
+      join("/root", "dir1"),
+      (path) => path === pathToTsconfig
+    );
+    // assert.equal(res, pathToTsconfig);
+    expect(res).toBe(pathToTsconfig);
+  });
+
+  it("should return undefined when reaching the top", () => {
+    const res = walkForTsConfig(join("/root", "dir1", "kalle"), () => false);
+    // assert.equal(res, undefined);
+    expect(res).toBeUndefined();
+  });
+});
+
+describe("loadConfig", () => {
+  it("should load a config", () => {
+    const config = { compilerOptions: { baseUrl: "hej" } };
+    const res = loadTsconfig(
+      "/root/dir1/tsconfig.json",
+      (path) => path === "/root/dir1/tsconfig.json",
+      (_) => JSON.stringify(config)
+    );
+    // assert.deepEqual(res, config);
+    expect(res).toStrictEqual(config);
+  });
+
+  it("should load a config with comments", () => {
+    const config = { compilerOptions: { baseUrl: "hej" } };
+    const res = loadTsconfig(
+      "/root/dir1/tsconfig.json",
+      (path) => path === "/root/dir1/tsconfig.json",
+      (_) => `{
+          // my comment
+          "compilerOptions": { 
+            "baseUrl": "hej"
+          }
+        }`
+    );
+    // assert.deepEqual(res, config);
+    expect(res).toStrictEqual(config);
+  });
+
+  it("should load a config with trailing commas", () => {
+    const config = { compilerOptions: { baseUrl: "hej" } };
+    const res = loadTsconfig(
+      "/root/dir1/tsconfig.json",
+      (path) => path === "/root/dir1/tsconfig.json",
+      (_) => `{
+          "compilerOptions": { 
+            "baseUrl": "hej",
+          },
+        }`
+    );
+    // assert.deepEqual(res, config);
+    expect(res).toStrictEqual(config);
+  });
+
+  it("should throw an error including the file path when encountering invalid JSON5", () => {
+    expect(() =>
+      loadTsconfig(
+        "/root/dir1/tsconfig.json",
+        (path) => path === "/root/dir1/tsconfig.json",
+        (_) => `{
+            "compilerOptions": {
+          }`
+      )
+    ).toThrowError(
+      "/root/dir1/tsconfig.json is malformed JSON5: invalid end of input at 3:12"
+    );
+  });
+
+  it("should load a config with string extends and overwrite all options", () => {
+    const firstConfig = {
+      extends: "../base-config.json",
+      compilerOptions: { baseUrl: "kalle", paths: { foo: ["bar2"] } },
+    };
+    const firstConfigPath = join("/root", "dir1", "tsconfig.json");
+    const baseConfig = {
+      compilerOptions: {
+        baseUrl: "olle",
+        paths: { foo: ["bar1"] },
+        strict: true,
+      },
+    };
+    const baseConfigPath = join("/root", "base-config.json");
+    const res = loadTsconfig(
+      join("/root", "dir1", "tsconfig.json"),
+      (path) => path === firstConfigPath || path === baseConfigPath,
+      (path) => {
+        if (path === firstConfigPath) {
+          return JSON.stringify(firstConfig);
+        }
+        if (path === baseConfigPath) {
+          return JSON.stringify(baseConfig);
+        }
+        return "";
+      }
+    );
+
+    // assert.deepEqual(res, {
+    //   extends: "../base-config.json",
+    //   compilerOptions: {
+    //     baseUrl: "kalle",
+    //     paths: { foo: ["bar2"] },
+    //     strict: true,
+    //   },
+    // });
+    expect(res).toEqual({
+      extends: "../base-config.json",
+      compilerOptions: {
+        baseUrl: "kalle",
+        paths: { foo: ["bar2"] },
+        strict: true,
+      },
+    });
+  });
+
+  it("should load a config with string extends from node_modules and overwrite all options", () => {
+    const firstConfig = {
+      extends: "my-package/base-config.json",
+      compilerOptions: { baseUrl: "kalle", paths: { foo: ["bar2"] } },
+    };
+    const firstConfigPath = join("/root", "dir1", "tsconfig.json");
+    const baseConfig = {
+      compilerOptions: {
+        baseUrl: "olle",
+        paths: { foo: ["bar1"] },
+        strict: true,
+      },
+    };
+    const baseConfigPath = join(
+      "/root",
+      "dir1",
+      "node_modules",
+      "my-package",
+      "base-config.json"
+    );
+    const res = loadTsconfig(
+      join("/root", "dir1", "tsconfig.json"),
+      (path) => path === firstConfigPath || path === baseConfigPath,
+      (path) => {
+        if (path === firstConfigPath) {
+          return JSON.stringify(firstConfig);
+        }
+        if (path === baseConfigPath) {
+          return JSON.stringify(baseConfig);
+        }
+        return "";
+      }
+    );
+
+    // assert.deepEqual(res, {
+    //   extends: "my-package/base-config.json",
+    //   compilerOptions: {
+    //     baseUrl: "kalle",
+    //     paths: { foo: ["bar2"] },
+    //     strict: true,
+    //   },
+    // });
+    expect(res).toEqual({
+      extends: "my-package/base-config.json",
+      compilerOptions: {
+        baseUrl: "kalle",
+        paths: { foo: ["bar2"] },
+        strict: true,
+      },
+    });
+  });
+
+  it("should use baseUrl relative to location of extended tsconfig", () => {
+    const firstConfig = { compilerOptions: { baseUrl: "." } };
+    const firstConfigPath = join("/root", "first-config.json");
+    const secondConfig = { extends: "../first-config.json" };
+    const secondConfigPath = join("/root", "dir1", "second-config.json");
+    const thirdConfig = { extends: "../second-config.json" };
+    const thirdConfigPath = join("/root", "dir1", "dir2", "third-config.json");
+    const res = loadTsconfig(
+      join("/root", "dir1", "dir2", "third-config.json"),
+      (path) =>
+        path === firstConfigPath ||
+        path === secondConfigPath ||
+        path === thirdConfigPath,
+      (path) => {
+        if (path === firstConfigPath) {
+          return JSON.stringify(firstConfig);
+        }
+        if (path === secondConfigPath) {
+          return JSON.stringify(secondConfig);
+        }
+        if (path === thirdConfigPath) {
+          return JSON.stringify(thirdConfig);
+        }
+        return "";
+      }
+    );
+
+    // assert.deepEqual(res, {
+    //   extends: "../second-config.json",
+    //   compilerOptions: { baseUrl: join("..", "..") },
+    // });
+    expect(res).toEqual({
+      extends: "../second-config.json",
+      compilerOptions: { baseUrl: join("..", "..") },
+    });
+  });
+
+  it("should load a config with array extends and overwrite all options", () => {
+    const baseConfig1 = {
+      compilerOptions: { baseUrl: ".", paths: { foo: ["bar"] } },
+    };
+    const baseConfig1Path = join("/root", "base-config-1.json");
+    const baseConfig2 = { compilerOptions: { baseUrl: "." } };
+    const baseConfig2Path = join("/root", "dir1", "base-config-2.json");
+    const baseConfig3 = {
+      compilerOptions: { baseUrl: ".", paths: { foo: ["bar2"] } },
+    };
+    const baseConfig3Path = join("/root", "dir1", "dir2", "base-config-3.json");
+    const actualConfig = {
+      extends: [
+        "./base-config-1.json",
+        "./dir1/base-config-2.json",
+        "./dir1/dir2/base-config-3.json",
+      ],
+    };
+    const actualConfigPath = join("/root", "tsconfig.json");
+
+    const res = loadTsconfig(
+      join("/root", "tsconfig.json"),
+      (path) =>
+        [
+          baseConfig1Path,
+          baseConfig2Path,
+          baseConfig3Path,
+          actualConfigPath,
+        ].indexOf(path) >= 0,
+      (path) => {
+        if (path === baseConfig1Path) {
+          return JSON.stringify(baseConfig1);
+        }
+        if (path === baseConfig2Path) {
+          return JSON.stringify(baseConfig2);
+        }
+        if (path === baseConfig3Path) {
+          return JSON.stringify(baseConfig3);
+        }
+        if (path === actualConfigPath) {
+          return JSON.stringify(actualConfig);
+        }
+        return "";
+      }
+    );
+
+    expect(res).toEqual({
+      extends: [
+        "./base-config-1.json",
+        "./dir1/base-config-2.json",
+        "./dir1/dir2/base-config-3.json",
+      ],
+      compilerOptions: {
+        baseUrl: join("dir1", "dir2"),
+        paths: { foo: ["bar2"] },
+      },
+    });
+  });
+
+  it("should load a config with array extends without .json extension", () => {
+    const baseConfig = {
+      compilerOptions: { baseUrl: ".", paths: { foo: ["bar"] } },
+    };
+    const baseConfigPath = join("/root", "base-config-1.json");
+    const actualConfig = { extends: ["./base-config-1"] };
+    const actualConfigPath = join("/root", "tsconfig.json");
+
+    const res = loadTsconfig(
+      join("/root", "tsconfig.json"),
+      (path) => [baseConfigPath, actualConfigPath].indexOf(path) >= 0,
+      (path) => {
+        if (path === baseConfigPath) {
+          return JSON.stringify(baseConfig);
+        }
+        if (path === actualConfigPath) {
+          return JSON.stringify(actualConfig);
+        }
+        return "";
+      }
+    );
+
+    expect(res).toEqual({
+      extends: ["./base-config-1"],
+      compilerOptions: {
+        baseUrl: ".",
+        paths: { foo: ["bar"] },
+      },
+    });
+  });
+});
Index: frontend/node_modules/tsconfig-paths/src/__tests__/tsconfig-named.json
===================================================================
--- frontend/node_modules/tsconfig-paths/src/__tests__/tsconfig-named.json	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/tsconfig-paths/src/__tests__/tsconfig-named.json	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,10 @@
+{
+  "extends": "../base-tsconfig.json",
+  "compilerOptions": {
+    "baseUrl": ".",
+    "module": "commonjs",
+    "target": "es6",
+    "sourceMap": true,
+    "outDir": "./js_out"
+  }
+}
Index: frontend/node_modules/tsconfig-paths/src/config-loader.ts
===================================================================
--- frontend/node_modules/tsconfig-paths/src/config-loader.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/tsconfig-paths/src/config-loader.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,97 @@
+import * as TsConfigLoader2 from "./tsconfig-loader";
+import * as path from "path";
+import { options } from "./options";
+
+export interface ExplicitParams {
+  baseUrl: string;
+  paths: { [key: string]: Array<string> };
+  mainFields?: Array<string>;
+  addMatchAll?: boolean;
+}
+
+export type TsConfigLoader = (
+  params: TsConfigLoader2.TsConfigLoaderParams
+) => TsConfigLoader2.TsConfigLoaderResult;
+
+export interface ConfigLoaderParams {
+  cwd: string;
+  explicitParams?: ExplicitParams;
+  tsConfigLoader?: TsConfigLoader;
+}
+
+export interface ConfigLoaderSuccessResult {
+  resultType: "success";
+  configFileAbsolutePath: string;
+  baseUrl: string;
+  absoluteBaseUrl: string;
+  paths: { [key: string]: Array<string> };
+  mainFields?: Array<string>;
+  addMatchAll?: boolean;
+}
+
+export interface ConfigLoaderFailResult {
+  resultType: "failed";
+  message: string;
+}
+
+export type ConfigLoaderResult =
+  | ConfigLoaderSuccessResult
+  | ConfigLoaderFailResult;
+
+export function loadConfig(cwd: string = options.cwd): ConfigLoaderResult {
+  return configLoader({ cwd: cwd });
+}
+
+export function configLoader({
+  cwd,
+  explicitParams,
+  tsConfigLoader = TsConfigLoader2.tsConfigLoader,
+}: ConfigLoaderParams): ConfigLoaderResult {
+  if (explicitParams) {
+    // tslint:disable-next-line:no-shadowed-variable
+    const absoluteBaseUrl = path.isAbsolute(explicitParams.baseUrl)
+      ? explicitParams.baseUrl
+      : path.join(cwd, explicitParams.baseUrl);
+
+    return {
+      resultType: "success",
+      configFileAbsolutePath: "",
+      baseUrl: explicitParams.baseUrl,
+      absoluteBaseUrl,
+      paths: explicitParams.paths,
+      mainFields: explicitParams.mainFields,
+      addMatchAll: explicitParams.addMatchAll,
+    };
+  }
+
+  // Load tsconfig and create path matching function
+  const loadResult = tsConfigLoader({
+    cwd,
+    getEnv: (key: string) => process.env[key],
+  });
+
+  if (!loadResult.tsConfigPath) {
+    return {
+      resultType: "failed",
+      message: "Couldn't find tsconfig.json",
+    };
+  }
+
+  if (!loadResult.baseUrl) {
+    return {
+      resultType: "failed",
+      message: "Missing baseUrl in compilerOptions",
+    };
+  }
+
+  const tsConfigDir = path.dirname(loadResult.tsConfigPath);
+  const absoluteBaseUrl = path.join(tsConfigDir, loadResult.baseUrl);
+
+  return {
+    resultType: "success",
+    configFileAbsolutePath: loadResult.tsConfigPath,
+    baseUrl: loadResult.baseUrl,
+    absoluteBaseUrl,
+    paths: loadResult.paths || {},
+  };
+}
Index: frontend/node_modules/tsconfig-paths/src/filesystem.ts
===================================================================
--- frontend/node_modules/tsconfig-paths/src/filesystem.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/tsconfig-paths/src/filesystem.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,87 @@
+import * as fs from "fs";
+
+/**
+ * Typing for the fields of package.json we care about
+ */
+export interface PackageJson {
+  [key: string]: string;
+}
+
+/**
+ * A function that json from a file
+ */
+export interface ReadJsonSync {
+  // tslint:disable-next-line:no-any
+  (packageJsonPath: string): any | undefined;
+}
+
+export interface FileExistsSync {
+  (name: string): boolean;
+}
+
+export interface FileExistsAsync {
+  (path: string, callback: (err?: Error, exists?: boolean) => void): void;
+}
+
+export interface ReadJsonAsyncCallback {
+  // tslint:disable-next-line:no-any
+  (err?: Error, content?: any): void;
+}
+
+export interface ReadJsonAsync {
+  (path: string, callback: ReadJsonAsyncCallback): void;
+}
+
+export function fileExistsSync(path: string): boolean {
+  try {
+    const stats = fs.statSync(path);
+    return stats.isFile();
+  } catch (err) {
+    // If error, assume file did not exist
+    return false;
+  }
+}
+
+/**
+ * Reads package.json from disk
+ * @param file Path to package.json
+ */
+// tslint:disable-next-line:no-any
+export function readJsonFromDiskSync(packageJsonPath: string): any | undefined {
+  if (!fs.existsSync(packageJsonPath)) {
+    return undefined;
+  }
+  return require(packageJsonPath);
+}
+
+export function readJsonFromDiskAsync(
+  path: string,
+  // tslint:disable-next-line:no-any
+  callback: (err?: Error, content?: any) => void
+): void {
+  fs.readFile(path, "utf8", (err, result) => {
+    // If error, assume file did not exist
+    if (err || !result) {
+      return callback();
+    }
+    const json = JSON.parse(result);
+    return callback(undefined, json);
+  });
+}
+
+export function fileExistsAsync(
+  path2: string,
+  callback2: (err?: Error, exists?: boolean) => void
+): void {
+  fs.stat(path2, (err: Error, stats: fs.Stats) => {
+    if (err) {
+      // If error assume file does not exist
+      return callback2(undefined, false);
+    }
+    callback2(undefined, stats ? stats.isFile() : false);
+  });
+}
+
+export function removeExtension(path: string): string {
+  return path.substring(0, path.lastIndexOf(".")) || path;
+}
Index: frontend/node_modules/tsconfig-paths/src/index.ts
===================================================================
--- frontend/node_modules/tsconfig-paths/src/index.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/tsconfig-paths/src/index.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,24 @@
+// register is used from register.js in root dir
+export {
+  createMatchPath,
+  matchFromAbsolutePaths,
+  MatchPath,
+} from "./match-path-sync";
+export {
+  createMatchPathAsync,
+  matchFromAbsolutePathsAsync,
+  MatchPathAsync,
+} from "./match-path-async";
+export { register } from "./register";
+export {
+  loadConfig,
+  ConfigLoaderResult,
+  ConfigLoaderSuccessResult,
+  ConfigLoaderFailResult,
+} from "./config-loader";
+export {
+  ReadJsonSync,
+  ReadJsonAsync,
+  FileExistsSync,
+  FileExistsAsync,
+} from "./filesystem";
Index: frontend/node_modules/tsconfig-paths/src/mapping-entry.ts
===================================================================
--- frontend/node_modules/tsconfig-paths/src/mapping-entry.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/tsconfig-paths/src/mapping-entry.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,64 @@
+import * as path from "path";
+
+export interface MappingEntry {
+  readonly pattern: string;
+  readonly paths: ReadonlyArray<string>;
+}
+
+export interface Paths {
+  readonly [key: string]: ReadonlyArray<string>;
+}
+
+/**
+ * Converts an absolute baseUrl and paths to an array of absolute mapping entries.
+ * The array is sorted by longest prefix.
+ * Having an array with entries allows us to keep a sorting order rather than
+ * sort by keys each time we use the mappings.
+ * @param absoluteBaseUrl
+ * @param paths
+ * @param addMatchAll
+ */
+export function getAbsoluteMappingEntries(
+  absoluteBaseUrl: string,
+  paths: Paths,
+  addMatchAll: boolean
+): ReadonlyArray<MappingEntry> {
+  // Resolve all paths to absolute form once here, and sort them by
+  // longest prefix once here, this saves time on each request later.
+  // We need to put them in an array to preserve the sorting order.
+  const sortedKeys = sortByLongestPrefix(Object.keys(paths));
+  const absolutePaths: Array<MappingEntry> = [];
+  for (const key of sortedKeys) {
+    absolutePaths.push({
+      pattern: key,
+      paths: paths[key].map((pathToResolve) =>
+        path.join(absoluteBaseUrl, pathToResolve)
+      ),
+    });
+  }
+  // If there is no match-all path specified in the paths section of tsconfig, then try to match
+  // all paths relative to baseUrl, this is how typescript works.
+  if (!paths["*"] && addMatchAll) {
+    absolutePaths.push({
+      pattern: "*",
+      paths: [`${absoluteBaseUrl.replace(/\/$/, "")}/*`],
+    });
+  }
+
+  return absolutePaths;
+}
+
+/**
+ * Sort path patterns.
+ * If a module name can be matched with multiple patterns then pattern with the longest prefix will be picked.
+ */
+function sortByLongestPrefix(arr: Array<string>): Array<string> {
+  return arr
+    .concat()
+    .sort((a: string, b: string) => getPrefixLength(b) - getPrefixLength(a));
+}
+
+function getPrefixLength(pattern: string): number {
+  const prefixLength = pattern.indexOf("*");
+  return pattern.substr(0, prefixLength).length;
+}
Index: frontend/node_modules/tsconfig-paths/src/match-path-async.ts
===================================================================
--- frontend/node_modules/tsconfig-paths/src/match-path-async.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/tsconfig-paths/src/match-path-async.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,218 @@
+import * as path from "path";
+import * as TryPath from "./try-path";
+import * as MappingEntry from "./mapping-entry";
+import * as Filesystem from "./filesystem";
+
+/**
+ * Function that can match a path async
+ */
+export interface MatchPathAsync {
+  (
+    requestedModule: string,
+    readJson: Filesystem.ReadJsonAsync | undefined,
+    fileExists: Filesystem.FileExistsAsync | undefined,
+    extensions: ReadonlyArray<string> | undefined,
+    callback: MatchPathAsyncCallback
+  ): void;
+}
+
+export interface MatchPathAsyncCallback {
+  (err?: Error, path?: string): void;
+}
+
+/**
+ * See the sync version for docs.
+ */
+export function createMatchPathAsync(
+  absoluteBaseUrl: string,
+  paths: { [key: string]: Array<string> },
+  mainFields: string[] = ["main"],
+  addMatchAll: boolean = true
+): MatchPathAsync {
+  const absolutePaths = MappingEntry.getAbsoluteMappingEntries(
+    absoluteBaseUrl,
+    paths,
+    addMatchAll
+  );
+
+  return (
+    requestedModule: string,
+    readJson: Filesystem.ReadJsonAsync | undefined,
+    fileExists: Filesystem.FileExistsAsync | undefined,
+    extensions: ReadonlyArray<string> | undefined,
+    callback: MatchPathAsyncCallback
+  ) =>
+    matchFromAbsolutePathsAsync(
+      absolutePaths,
+      requestedModule,
+      readJson,
+      fileExists,
+      extensions,
+      callback,
+      mainFields
+    );
+}
+
+/**
+ * See the sync version for docs.
+ */
+export function matchFromAbsolutePathsAsync(
+  absolutePathMappings: ReadonlyArray<MappingEntry.MappingEntry>,
+  requestedModule: string,
+  readJson: Filesystem.ReadJsonAsync = Filesystem.readJsonFromDiskAsync,
+  fileExists: Filesystem.FileExistsAsync = Filesystem.fileExistsAsync,
+  extensions: ReadonlyArray<string> = Object.keys(require.extensions),
+  callback: MatchPathAsyncCallback,
+  mainFields: string[] = ["main"]
+): void {
+  const tryPaths = TryPath.getPathsToTry(
+    extensions,
+    absolutePathMappings,
+    requestedModule
+  );
+
+  if (!tryPaths) {
+    return callback();
+  }
+
+  findFirstExistingPath(
+    tryPaths,
+    readJson,
+    fileExists,
+    callback,
+    0,
+    mainFields
+  );
+}
+
+function findFirstExistingMainFieldMappedFile(
+  packageJson: Filesystem.PackageJson,
+  mainFields: string[],
+  packageJsonPath: string,
+  fileExistsAsync: Filesystem.FileExistsAsync,
+  doneCallback: (err?: Error, filepath?: string) => void,
+  index: number = 0
+): void {
+  if (index >= mainFields.length) {
+    return doneCallback(undefined, undefined);
+  }
+
+  const tryNext = () =>
+    findFirstExistingMainFieldMappedFile(
+      packageJson,
+      mainFields,
+      packageJsonPath,
+      fileExistsAsync,
+      doneCallback,
+      index + 1
+    );
+
+  const mainFieldMapping = packageJson[mainFields[index]];
+  if (typeof mainFieldMapping !== "string") {
+    // Skip mappings that are not pointers to replacement files
+    return tryNext();
+  }
+
+  const mappedFilePath = path.join(
+    path.dirname(packageJsonPath),
+    mainFieldMapping
+  );
+  fileExistsAsync(mappedFilePath, (err?: Error, exists?: boolean) => {
+    if (err) {
+      return doneCallback(err);
+    }
+    if (exists) {
+      return doneCallback(undefined, mappedFilePath);
+    }
+    return tryNext();
+  });
+}
+
+// Recursive loop to probe for physical files
+function findFirstExistingPath(
+  tryPaths: ReadonlyArray<TryPath.TryPath>,
+  readJson: Filesystem.ReadJsonAsync,
+  fileExists: Filesystem.FileExistsAsync,
+  doneCallback: MatchPathAsyncCallback,
+  index: number = 0,
+  mainFields: string[] = ["main"]
+): void {
+  const tryPath = tryPaths[index];
+  if (
+    tryPath.type === "file" ||
+    tryPath.type === "extension" ||
+    tryPath.type === "index"
+  ) {
+    fileExists(tryPath.path, (err: Error, exists: boolean) => {
+      if (err) {
+        return doneCallback(err);
+      }
+      if (exists) {
+        return doneCallback(undefined, TryPath.getStrippedPath(tryPath));
+      }
+      if (index === tryPaths.length - 1) {
+        return doneCallback();
+      }
+      // Continue with the next path
+      return findFirstExistingPath(
+        tryPaths,
+        readJson,
+        fileExists,
+        doneCallback,
+        index + 1,
+        mainFields
+      );
+    });
+  } else if (tryPath.type === "package") {
+    readJson(tryPath.path, (err, packageJson) => {
+      if (err) {
+        return doneCallback(err);
+      }
+      if (packageJson) {
+        return findFirstExistingMainFieldMappedFile(
+          packageJson,
+          mainFields,
+          tryPath.path,
+          fileExists,
+          (mainFieldErr?: Error, mainFieldMappedFile?: string) => {
+            if (mainFieldErr) {
+              return doneCallback(mainFieldErr);
+            }
+            if (mainFieldMappedFile) {
+              return doneCallback(undefined, mainFieldMappedFile);
+            }
+
+            // No field in package json was a valid option. Continue with the next path.
+            return findFirstExistingPath(
+              tryPaths,
+              readJson,
+              fileExists,
+              doneCallback,
+              index + 1,
+              mainFields
+            );
+          }
+        );
+      }
+
+      // This is async code, we need to return unconditionally, otherwise the code still falls
+      // through and keeps recursing. While this might work in general, libraries that use neo-async
+      // like Webpack will actually not allow you to call the same callback twice.
+      //
+      // An example of where this caused issues:
+      // https://github.com/dividab/tsconfig-paths-webpack-plugin/issues/11
+      //
+      // Continue with the next path
+      return findFirstExistingPath(
+        tryPaths,
+        readJson,
+        fileExists,
+        doneCallback,
+        index + 1,
+        mainFields
+      );
+    });
+  } else {
+    TryPath.exhaustiveTypeException(tryPath.type);
+  }
+}
Index: frontend/node_modules/tsconfig-paths/src/match-path-sync.ts
===================================================================
--- frontend/node_modules/tsconfig-paths/src/match-path-sync.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/tsconfig-paths/src/match-path-sync.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,141 @@
+import * as path from "path";
+import * as Filesystem from "./filesystem";
+import * as MappingEntry from "./mapping-entry";
+import * as TryPath from "./try-path";
+
+/**
+ * Function that can match a path
+ */
+export interface MatchPath {
+  (
+    requestedModule: string,
+    readJson?: Filesystem.ReadJsonSync,
+    fileExists?: (name: string) => boolean,
+    extensions?: ReadonlyArray<string>
+  ): string | undefined;
+}
+
+/**
+ * Creates a function that can resolve paths according to tsconfig paths property.
+ * @param absoluteBaseUrl Absolute version of baseUrl as specified in tsconfig.
+ * @param paths The paths as specified in tsconfig.
+ * @param mainFields A list of package.json field names to try when resolving module files.
+ * @param addMatchAll Add a match-all "*" rule if none is present
+ * @returns a function that can resolve paths.
+ */
+export function createMatchPath(
+  absoluteBaseUrl: string,
+  paths: { [key: string]: Array<string> },
+  mainFields: string[] = ["main"],
+  addMatchAll: boolean = true
+): MatchPath {
+  const absolutePaths = MappingEntry.getAbsoluteMappingEntries(
+    absoluteBaseUrl,
+    paths,
+    addMatchAll
+  );
+
+  return (
+    requestedModule: string,
+    readJson?: Filesystem.ReadJsonSync,
+    fileExists?: Filesystem.FileExistsSync,
+    extensions?: Array<string>
+  ) =>
+    matchFromAbsolutePaths(
+      absolutePaths,
+      requestedModule,
+      readJson,
+      fileExists,
+      extensions,
+      mainFields
+    );
+}
+
+/**
+ * Finds a path from tsconfig that matches a module load request.
+ * @param absolutePathMappings The paths to try as specified in tsconfig but resolved to absolute form.
+ * @param requestedModule The required module name.
+ * @param readJson Function that can read json from a path (useful for testing).
+ * @param fileExists Function that checks for existence of a file at a path (useful for testing).
+ * @param extensions File extensions to probe for (useful for testing).
+ * @param mainFields A list of package.json field names to try when resolving module files.
+ * @returns the found path, or undefined if no path was found.
+ */
+export function matchFromAbsolutePaths(
+  absolutePathMappings: ReadonlyArray<MappingEntry.MappingEntry>,
+  requestedModule: string,
+  readJson: Filesystem.ReadJsonSync = Filesystem.readJsonFromDiskSync,
+  fileExists: Filesystem.FileExistsSync = Filesystem.fileExistsSync,
+  extensions: Array<string> = Object.keys(require.extensions),
+  mainFields: string[] = ["main"]
+): string | undefined {
+  const tryPaths = TryPath.getPathsToTry(
+    extensions,
+    absolutePathMappings,
+    requestedModule
+  );
+
+  if (!tryPaths) {
+    return undefined;
+  }
+
+  return findFirstExistingPath(tryPaths, readJson, fileExists, mainFields);
+}
+
+function findFirstExistingMainFieldMappedFile(
+  packageJson: Filesystem.PackageJson,
+  mainFields: string[],
+  packageJsonPath: string,
+  fileExists: Filesystem.FileExistsSync
+): string | undefined {
+  for (let index = 0; index < mainFields.length; index++) {
+    const mainFieldName = mainFields[index];
+    const candidateMapping = packageJson[mainFieldName];
+    if (candidateMapping && typeof candidateMapping === "string") {
+      const candidateFilePath = path.join(
+        path.dirname(packageJsonPath),
+        candidateMapping
+      );
+      if (fileExists(candidateFilePath)) {
+        return candidateFilePath;
+      }
+    }
+  }
+
+  return undefined;
+}
+
+function findFirstExistingPath(
+  tryPaths: ReadonlyArray<TryPath.TryPath>,
+  readJson: Filesystem.ReadJsonSync = Filesystem.readJsonFromDiskSync,
+  fileExists: Filesystem.FileExistsSync,
+  mainFields: string[] = ["main"]
+): string | undefined {
+  for (const tryPath of tryPaths) {
+    if (
+      tryPath.type === "file" ||
+      tryPath.type === "extension" ||
+      tryPath.type === "index"
+    ) {
+      if (fileExists(tryPath.path)) {
+        return TryPath.getStrippedPath(tryPath);
+      }
+    } else if (tryPath.type === "package") {
+      const packageJson: Filesystem.PackageJson = readJson(tryPath.path);
+      if (packageJson) {
+        const mainFieldMappedFile = findFirstExistingMainFieldMappedFile(
+          packageJson,
+          mainFields,
+          tryPath.path,
+          fileExists
+        );
+        if (mainFieldMappedFile) {
+          return mainFieldMappedFile;
+        }
+      }
+    } else {
+      TryPath.exhaustiveTypeException(tryPath.type);
+    }
+  }
+  return undefined;
+}
Index: frontend/node_modules/tsconfig-paths/src/options.ts
===================================================================
--- frontend/node_modules/tsconfig-paths/src/options.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/tsconfig-paths/src/options.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,18 @@
+import * as minimist from "minimist";
+
+const argv = minimist(process.argv.slice(2), {
+  string: ["project"],
+  alias: {
+    project: ["P"],
+  },
+});
+
+const project = argv && argv.project;
+
+export interface Options {
+  cwd: string;
+}
+
+export const options: Options = {
+  cwd: project || process.cwd(),
+};
Index: frontend/node_modules/tsconfig-paths/src/register.ts
===================================================================
--- frontend/node_modules/tsconfig-paths/src/register.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/tsconfig-paths/src/register.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,97 @@
+import { createMatchPath } from "./match-path-sync";
+import { configLoader, ExplicitParams } from "./config-loader";
+import { options } from "./options";
+
+const noOp = (): void => void 0;
+
+function getCoreModules(
+  builtinModules: string[] | undefined
+): { [key: string]: boolean } {
+  builtinModules = builtinModules || [
+    "assert",
+    "buffer",
+    "child_process",
+    "cluster",
+    "crypto",
+    "dgram",
+    "dns",
+    "domain",
+    "events",
+    "fs",
+    "http",
+    "https",
+    "net",
+    "os",
+    "path",
+    "punycode",
+    "querystring",
+    "readline",
+    "stream",
+    "string_decoder",
+    "tls",
+    "tty",
+    "url",
+    "util",
+    "v8",
+    "vm",
+    "zlib",
+  ];
+
+  const coreModules: { [key: string]: boolean } = {};
+  for (let module of builtinModules) {
+    coreModules[module] = true;
+  }
+
+  return coreModules;
+}
+
+/**
+ * Installs a custom module load function that can adhere to paths in tsconfig.
+ * Returns a function to undo paths registration.
+ */
+export function register(explicitParams: ExplicitParams): () => void {
+  const configLoaderResult = configLoader({
+    cwd: options.cwd,
+    explicitParams,
+  });
+
+  if (configLoaderResult.resultType === "failed") {
+    console.warn(
+      `${configLoaderResult.message}. tsconfig-paths will be skipped`
+    );
+
+    return noOp;
+  }
+
+  const matchPath = createMatchPath(
+    configLoaderResult.absoluteBaseUrl,
+    configLoaderResult.paths,
+    configLoaderResult.mainFields,
+    configLoaderResult.addMatchAll
+  );
+
+  // Patch node's module loading
+  // tslint:disable-next-line:no-require-imports variable-name
+  const Module = require("module");
+  const originalResolveFilename = Module._resolveFilename;
+  const coreModules = getCoreModules(Module.builtinModules);
+  // tslint:disable-next-line:no-any
+  Module._resolveFilename = function (request: string, _parent: any): string {
+    const isCoreModule = coreModules.hasOwnProperty(request);
+    if (!isCoreModule) {
+      const found = matchPath(request);
+      if (found) {
+        const modifiedArguments = [found, ...[].slice.call(arguments, 1)]; // Passes all arguments. Even those that is not specified above.
+        // tslint:disable-next-line:no-invalid-this
+        return originalResolveFilename.apply(this, modifiedArguments);
+      }
+    }
+    // tslint:disable-next-line:no-invalid-this
+    return originalResolveFilename.apply(this, arguments);
+  };
+
+  return () => {
+    // Return node's module loading to original state.
+    Module._resolveFilename = originalResolveFilename;
+  };
+}
Index: frontend/node_modules/tsconfig-paths/src/try-path.ts
===================================================================
--- frontend/node_modules/tsconfig-paths/src/try-path.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/tsconfig-paths/src/try-path.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,102 @@
+import * as path from "path";
+import { MappingEntry } from "./mapping-entry";
+import { dirname } from "path";
+import { removeExtension } from "./filesystem";
+
+export interface TryPath {
+  readonly type: "file" | "extension" | "index" | "package";
+  readonly path: string;
+}
+
+/**
+ * Builds a list of all physical paths to try by:
+ * 1. Check for file named exactly as request.
+ * 2. Check for files named as request ending in any of the extensions.
+ * 3. Check for file specified in package.json's main property.
+ * 4. Check for files named as request ending in "index" with any of the extensions.
+ */
+export function getPathsToTry(
+  extensions: ReadonlyArray<string>,
+  absolutePathMappings: ReadonlyArray<MappingEntry>,
+  requestedModule: string
+): ReadonlyArray<TryPath> | undefined {
+  if (!absolutePathMappings || !requestedModule || requestedModule[0] === ".") {
+    return undefined;
+  }
+
+  const pathsToTry: Array<TryPath> = [];
+  for (const entry of absolutePathMappings) {
+    const starMatch =
+      entry.pattern === requestedModule
+        ? ""
+        : matchStar(entry.pattern, requestedModule);
+    if (starMatch !== undefined) {
+      for (const physicalPathPattern of entry.paths) {
+        const physicalPath = physicalPathPattern.replace("*", starMatch);
+        pathsToTry.push({ type: "file", path: physicalPath });
+        pathsToTry.push(
+          ...extensions.map(
+            (e) => ({ type: "extension", path: physicalPath + e } as TryPath)
+          )
+        );
+        pathsToTry.push({
+          type: "package",
+          path: path.join(physicalPath, "/package.json"),
+        });
+        const indexPath = path.join(physicalPath, "/index");
+        pathsToTry.push(
+          ...extensions.map(
+            (e) => ({ type: "index", path: indexPath + e } as TryPath)
+          )
+        );
+      }
+    }
+  }
+  return pathsToTry.length === 0 ? undefined : pathsToTry;
+}
+
+// Not sure why we don't just return the full found path?
+export function getStrippedPath(tryPath: TryPath): string {
+  return tryPath.type === "index"
+    ? dirname(tryPath.path)
+    : tryPath.type === "file"
+    ? tryPath.path
+    : tryPath.type === "extension"
+    ? removeExtension(tryPath.path)
+    : tryPath.type === "package"
+    ? tryPath.path
+    : exhaustiveTypeException(tryPath.type);
+}
+
+export function exhaustiveTypeException(check: never): never {
+  throw new Error(`Unknown type ${check}`);
+}
+
+/**
+ * Matches pattern with a single star against search.
+ * Star must match at least one character to be considered a match.
+ * @param patttern for example "foo*"
+ * @param search for example "fooawesomebar"
+ * @returns the part of search that * matches, or undefined if no match.
+ */
+function matchStar(pattern: string, search: string): string | undefined {
+  if (search.length < pattern.length) {
+    return undefined;
+  }
+  if (pattern === "*") {
+    return search;
+  }
+  const star = pattern.indexOf("*");
+  if (star === -1) {
+    return undefined;
+  }
+  const part1 = pattern.substring(0, star);
+  const part2 = pattern.substring(star + 1);
+  if (search.substr(0, star) !== part1) {
+    return undefined;
+  }
+  if (search.substr(search.length - part2.length) !== part2) {
+    return undefined;
+  }
+  return search.substr(star, search.length - part2.length);
+}
Index: frontend/node_modules/tsconfig-paths/src/tsconfig-loader.ts
===================================================================
--- frontend/node_modules/tsconfig-paths/src/tsconfig-loader.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/tsconfig-paths/src/tsconfig-loader.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,226 @@
+import * as path from "path";
+import * as fs from "fs";
+// tslint:disable:no-require-imports
+import JSON5 = require("json5");
+import StripBom = require("strip-bom");
+// tslint:enable:no-require-imports
+
+/**
+ * Typing for the parts of tsconfig that we care about
+ */
+export interface Tsconfig {
+  extends?: string | string[];
+  compilerOptions?: {
+    baseUrl?: string;
+    paths?: { [key: string]: Array<string> };
+    strict?: boolean;
+  };
+}
+
+export interface TsConfigLoaderResult {
+  tsConfigPath: string | undefined;
+  baseUrl: string | undefined;
+  paths: { [key: string]: Array<string> } | undefined;
+}
+
+export interface TsConfigLoaderParams {
+  getEnv: (key: string) => string | undefined;
+  cwd: string;
+  loadSync?(
+    cwd: string,
+    filename?: string,
+    baseUrl?: string
+  ): TsConfigLoaderResult;
+}
+
+export function tsConfigLoader({
+  getEnv,
+  cwd,
+  loadSync = loadSyncDefault,
+}: TsConfigLoaderParams): TsConfigLoaderResult {
+  const TS_NODE_PROJECT = getEnv("TS_NODE_PROJECT");
+  const TS_NODE_BASEURL = getEnv("TS_NODE_BASEURL");
+
+  // tsconfig.loadSync handles if TS_NODE_PROJECT is a file or directory
+  // and also overrides baseURL if TS_NODE_BASEURL is available.
+  const loadResult = loadSync(cwd, TS_NODE_PROJECT, TS_NODE_BASEURL);
+  return loadResult;
+}
+
+function loadSyncDefault(
+  cwd: string,
+  filename?: string,
+  baseUrl?: string
+): TsConfigLoaderResult {
+  // Tsconfig.loadSync uses path.resolve. This is why we can use an absolute path as filename
+
+  const configPath = resolveConfigPath(cwd, filename);
+
+  if (!configPath) {
+    return {
+      tsConfigPath: undefined,
+      baseUrl: undefined,
+      paths: undefined,
+    };
+  }
+  const config = loadTsconfig(configPath);
+
+  return {
+    tsConfigPath: configPath,
+    baseUrl:
+      baseUrl ||
+      (config && config.compilerOptions && config.compilerOptions.baseUrl),
+    paths: config && config.compilerOptions && config.compilerOptions.paths,
+  };
+}
+
+function resolveConfigPath(cwd: string, filename?: string): string | undefined {
+  if (filename) {
+    const absolutePath = fs.lstatSync(filename).isDirectory()
+      ? path.resolve(filename, "./tsconfig.json")
+      : path.resolve(cwd, filename);
+
+    return absolutePath;
+  }
+
+  if (fs.statSync(cwd).isFile()) {
+    return path.resolve(cwd);
+  }
+
+  const configAbsolutePath = walkForTsConfig(cwd);
+  return configAbsolutePath ? path.resolve(configAbsolutePath) : undefined;
+}
+
+export function walkForTsConfig(
+  directory: string,
+  existsSync: (path: string) => boolean = fs.existsSync
+): string | undefined {
+  const configPath = path.join(directory, "./tsconfig.json");
+  if (existsSync(configPath)) {
+    return configPath;
+  }
+
+  const parentDirectory = path.join(directory, "../");
+
+  // If we reached the top
+  if (directory === parentDirectory) {
+    return undefined;
+  }
+
+  return walkForTsConfig(parentDirectory, existsSync);
+}
+
+export function loadTsconfig(
+  configFilePath: string,
+  existsSync: (path: string) => boolean = fs.existsSync,
+  readFileSync: (filename: string) => string = (filename: string) =>
+    fs.readFileSync(filename, "utf8")
+): Tsconfig | undefined {
+  if (!existsSync(configFilePath)) {
+    return undefined;
+  }
+
+  const configString = readFileSync(configFilePath);
+  const cleanedJson = StripBom(configString);
+  let config: Tsconfig;
+  try {
+    config = JSON5.parse(cleanedJson);
+  } catch (e) {
+    throw new Error(`${configFilePath} is malformed ${e.message}`);
+  }
+
+  let extendedConfig = config.extends;
+  if (extendedConfig) {
+    let base: Tsconfig;
+
+    if (Array.isArray(extendedConfig)) {
+      base = extendedConfig.reduce(
+        (currBase, extendedConfigElement) =>
+          mergeTsconfigs(
+            currBase,
+            loadTsconfigFromExtends(
+              configFilePath,
+              extendedConfigElement,
+              existsSync,
+              readFileSync
+            )
+          ),
+        {}
+      );
+    } else {
+      base = loadTsconfigFromExtends(
+        configFilePath,
+        extendedConfig,
+        existsSync,
+        readFileSync
+      );
+    }
+
+    return mergeTsconfigs(base, config);
+  }
+  return config;
+}
+
+/**
+ * Intended to be called only from loadTsconfig.
+ * Parameters don't have defaults because they should use the same as loadTsconfig.
+ */
+function loadTsconfigFromExtends(
+  configFilePath: string,
+  extendedConfigValue: string,
+  // eslint-disable-next-line no-shadow
+  existsSync: (path: string) => boolean,
+  readFileSync: (filename: string) => string
+): Tsconfig {
+  if (
+    typeof extendedConfigValue === "string" &&
+    extendedConfigValue.indexOf(".json") === -1
+  ) {
+    extendedConfigValue += ".json";
+  }
+  const currentDir = path.dirname(configFilePath);
+  let extendedConfigPath = path.join(currentDir, extendedConfigValue);
+  if (
+    extendedConfigValue.indexOf("/") !== -1 &&
+    extendedConfigValue.indexOf(".") !== -1 &&
+    !existsSync(extendedConfigPath)
+  ) {
+    extendedConfigPath = path.join(
+      currentDir,
+      "node_modules",
+      extendedConfigValue
+    );
+  }
+
+  const config =
+    loadTsconfig(extendedConfigPath, existsSync, readFileSync) || {};
+
+  // baseUrl should be interpreted as relative to extendedConfigPath,
+  // but we need to update it so it is relative to the original tsconfig being loaded
+  if (config.compilerOptions?.baseUrl) {
+    const extendsDir = path.dirname(extendedConfigValue);
+    config.compilerOptions.baseUrl = path.join(
+      extendsDir,
+      config.compilerOptions.baseUrl
+    );
+  }
+
+  return config;
+}
+
+function mergeTsconfigs(
+  base: Tsconfig | undefined,
+  config: Tsconfig | undefined
+): Tsconfig {
+  base = base || {};
+  config = config || {};
+
+  return {
+    ...base,
+    ...config,
+    compilerOptions: {
+      ...base.compilerOptions,
+      ...config.compilerOptions,
+    },
+  };
+}
