Browse Source

Test app configs

Nicolas Meienberger 3 years ago
parent
commit
71aaa7f03b

+ 3 - 0
.github/workflows/ci.yml

@@ -42,6 +42,9 @@ jobs:
       - name: Install dependencies
         run: pnpm install
 
+      - name: Run global tests
+        run: pnpm test
+
       - name: Run tests
         run: pnpm -r lint
       

+ 146 - 0
apps/__tests__/apps.test.ts

@@ -0,0 +1,146 @@
+import fs from "fs";
+import jsyaml from "js-yaml";
+
+interface AppConfig {
+  id: string;
+  port: number;
+  requirements?: {
+    ports?: number[];
+  };
+  name: string;
+  description: string;
+  version: string;
+  image: string;
+  short_desc: string;
+  author: string;
+  source: string;
+  available: boolean;
+}
+
+const networkExceptions = ["pihole", "tailscale"];
+const getAppConfigs = (): AppConfig[] => {
+  const apps: AppConfig[] = [];
+
+  const appsDir = fs.readdirSync("./apps");
+
+  appsDir.forEach((app) => {
+    const path = `./apps/${app}/config.json`;
+
+    if (fs.existsSync(path)) {
+      const configFile = fs.readFileSync(path).toString();
+      const config: AppConfig = JSON.parse(configFile);
+
+      if (config.available) {
+        apps.push(config);
+      }
+    }
+  });
+
+  return apps;
+};
+
+describe("App configs", () => {
+  it("Get app config should return at least one app", () => {
+    const apps = getAppConfigs();
+
+    expect(apps.length).toBeGreaterThan(0);
+  });
+
+  it("Each app should have an id", () => {
+    const apps = getAppConfigs();
+
+    apps.forEach((app) => {
+      expect(app.id).toBeDefined();
+    });
+  });
+
+  it("Each app should have a name", () => {
+    const apps = getAppConfigs();
+
+    apps.forEach((app) => {
+      expect(app.name).toBeDefined();
+    });
+  });
+
+  it("Each app should have a description", () => {
+    const apps = getAppConfigs();
+
+    apps.forEach((app) => {
+      expect(app.description).toBeDefined();
+    });
+  });
+
+  it("Each app should have a port", () => {
+    const apps = getAppConfigs();
+
+    apps.forEach((app) => {
+      expect(app.port).toBeDefined();
+      expect(app.port).toBeGreaterThan(999);
+      expect(app.port).toBeLessThan(65535);
+    });
+  });
+
+  it("Each app should have a different port", () => {
+    const appConfigs = getAppConfigs();
+    const ports = appConfigs.map((app) => app.port);
+    expect(new Set(ports).size).toBe(appConfigs.length);
+  });
+
+  it("Each app should have a unique id", () => {
+    const appConfigs = getAppConfigs();
+    const ids = appConfigs.map((app) => app.id);
+    expect(new Set(ids).size).toBe(appConfigs.length);
+  });
+
+  it("Each app should have a docker-compose file beside it", () => {
+    const apps = getAppConfigs();
+
+    apps.forEach((app) => {
+      expect(fs.existsSync(`./apps/${app.id}/docker-compose.yml`)).toBe(true);
+    });
+  });
+
+  it("Each app should have a container name equals to its id", () => {
+    const apps = getAppConfigs();
+
+    apps.forEach((app) => {
+      const dockerComposeFile = fs
+        .readFileSync(`./apps/${app.id}/docker-compose.yml`)
+        .toString();
+
+      const dockerCompose: any = jsyaml.load(dockerComposeFile);
+
+      if (!dockerCompose.services[app.id]) {
+        console.error(app.id);
+      }
+
+      expect(dockerCompose.services[app.id]).toBeDefined();
+      expect(dockerCompose.services[app.id].container_name).toBe(app.id);
+    });
+  });
+
+  it("Each app should have network tipi_main_network", () => {
+    const apps = getAppConfigs();
+
+    apps.forEach((app) => {
+      if (!networkExceptions.includes(app.id)) {
+        const dockerComposeFile = fs
+          .readFileSync(`./apps/${app.id}/docker-compose.yml`)
+          .toString();
+
+        const dockerCompose: any = jsyaml.load(dockerComposeFile);
+
+        expect(dockerCompose.services[app.id]).toBeDefined();
+
+        if (!dockerCompose.services[app.id].networks) {
+          console.error(app.id);
+        }
+
+        expect(dockerCompose.services[app.id].networks).toBeDefined();
+        expect(dockerCompose.services[app.id].networks).toStrictEqual([
+          "tipi_main_network",
+        ]);
+      }
+    });
+  });
+});

+ 1 - 1
apps/adguard/docker-compose.yml

@@ -1,7 +1,7 @@
 
 version: "3.7"
 services:
-  adguardhome:
+  adguard:
     image: adguard/adguardhome:v0.107.6
     container_name: adguard
     volumes:

+ 2 - 0
apps/invidious/docker-compose.arm.yml

@@ -25,6 +25,8 @@ services:
       retries: 2
     depends_on:
       - invidious-db
+    networks:
+      - tipi_main_network
 
   invidious-db:
     user: 1000:1000

+ 2 - 0
apps/invidious/docker-compose.yml

@@ -26,6 +26,8 @@ services:
       retries: 2
     depends_on:
       - invidious-db
+    networks:
+      - tipi_main_network
 
   invidious-db:
     user: 1000:1000

+ 1 - 1
apps/sonarr/docker-compose.yml

@@ -1,6 +1,6 @@
 version: "3.7"
 services:
-  radarr:
+  sonarr:
     image: lscr.io/linuxserver/sonarr
     container_name: sonarr
     environment:

+ 7 - 0
jest.config.js

@@ -0,0 +1,7 @@
+/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
+module.exports = {
+  preset: "ts-jest",
+  testEnvironment: "node",
+  testMatch: ["**/__tests__/**/*.test.ts"],
+  testPathIgnorePatterns: ["/node_modules/", "/packages/"],
+};

+ 9 - 2
package.json

@@ -3,6 +3,7 @@
   "version": "0.2.0",
   "description": "A homeserver for everyone",
   "scripts": {
+    "test": "jest",
     "prepare": "husky install",
     "act:test-install": "act --container-architecture linux/amd64 -j test-install",
     "act:docker": "act --container-architecture linux/amd64 --secret-file github.secrets -j docker",
@@ -10,9 +11,15 @@
     "start:rc": "docker-compose -f docker-compose.rc.yml --env-file .env up --build",
     "start:prod": "docker-compose --env-file .env up --build"
   },
-  "dependencies": {},
   "devDependencies": {
-    "husky": "^8.0.1"
+    "@types/jest": "^27.5.0",
+    "@types/js-yaml": "^4.0.5",
+    "@types/node": "17.0.31",
+    "husky": "^8.0.1",
+    "jest": "^28.1.0",
+    "js-yaml": "^4.1.0",
+    "ts-jest": "^28.0.2",
+    "typescript": "4.6.4"
   },
   "repository": {
     "type": "git",

+ 188 - 58
pnpm-lock.yaml

@@ -4,9 +4,23 @@ importers:
 
   .:
     specifiers:
+      '@types/jest': ^27.5.0
+      '@types/js-yaml': ^4.0.5
+      '@types/node': 17.0.31
       husky: ^8.0.1
+      jest: ^28.1.0
+      js-yaml: ^4.1.0
+      ts-jest: ^28.0.2
+      typescript: 4.6.4
     devDependencies:
+      '@types/jest': 27.5.0
+      '@types/js-yaml': 4.0.5
+      '@types/node': 17.0.31
       husky: 8.0.1
+      jest: 28.1.0_@types+node@17.0.31
+      js-yaml: 4.1.0
+      ts-jest: 28.0.2_z3fx76c5ksuwr36so7o5uc2kcy
+      typescript: 4.6.4
 
   packages/dashboard:
     specifiers:
@@ -78,7 +92,7 @@ importers:
       eslint: 8.12.0
       eslint-config-airbnb-typescript: 17.0.0_r46exuh3jlhq2wmrnqx2ufqspa
       eslint-config-next: 12.1.4_e6a2zi6fqdwfehht5cxvkmo3zu
-      eslint-plugin-import: 2.26.0_eslint@8.12.0
+      eslint-plugin-import: 2.26.0_hhyjdrupy4c2vgtpytri6cjwoy
       postcss: 8.4.13
       tailwindcss: 3.0.24
       typescript: 4.6.4
@@ -171,7 +185,7 @@ importers:
       eslint: 8.15.0
       eslint-config-airbnb-typescript: 17.0.0_c2ouaf3l4ivgkc6ae4nebvztom
       eslint-config-prettier: 8.5.0_eslint@8.15.0
-      eslint-plugin-import: 2.26.0_eslint@8.15.0
+      eslint-plugin-import: 2.26.0_6nacgdzqm4zbhelsxkmd2vkvxy
       eslint-plugin-prettier: 4.0.0_iqftbjqlxzn3ny5nablrkczhqi
       jest: 28.1.0
       nodemon: 2.0.16
@@ -2104,6 +2118,10 @@ packages:
     resolution: {integrity: sha512-6+0ekgfusHftJNYpihfkMu8BWdeHs9EOJuGcSofErjstGPfPGEu9yTu4t460lTzzAMl2cM5zngQJqPMHbbnvYA==}
     dev: true
 
+  /@types/js-yaml/4.0.5:
+    resolution: {integrity: sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==}
+    dev: true
+
   /@types/json-buffer/3.0.0:
     resolution: {integrity: sha512-3YP80IxxFJB4b5tYC2SUPwkg0XQLiu0nWvhRgEatgjf+29IcWO9X1k8xRv5DGssJ/lCrjYTjQPcobJr2yWIVuQ==}
     dev: false
@@ -3299,6 +3317,11 @@ packages:
 
   /debug/2.6.9:
     resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
+    peerDependencies:
+      supports-color: '*'
+    peerDependenciesMeta:
+      supports-color:
+        optional: true
     dependencies:
       ms: 2.0.0
 
@@ -3826,7 +3849,7 @@ packages:
     dependencies:
       confusing-browser-globals: 1.0.11
       eslint: 8.15.0
-      eslint-plugin-import: 2.26.0_eslint@8.15.0
+      eslint-plugin-import: 2.26.0_6nacgdzqm4zbhelsxkmd2vkvxy
       object.assign: 4.1.2
       object.entries: 1.1.5
       semver: 6.3.0
@@ -3841,7 +3864,7 @@ packages:
     dependencies:
       confusing-browser-globals: 1.0.11
       eslint: 8.12.0
-      eslint-plugin-import: 2.26.0_eslint@8.12.0
+      eslint-plugin-import: 2.26.0_hhyjdrupy4c2vgtpytri6cjwoy
       object.assign: 4.1.2
       object.entries: 1.1.5
       semver: 6.3.0
@@ -3859,7 +3882,7 @@ packages:
       '@typescript-eslint/parser': 5.22.0_hcfsmds2fshutdssjqluwm76uu
       eslint: 8.15.0
       eslint-config-airbnb-base: 15.0.0_gwd37gqv3vjv3xlpl7ju3ag2qu
-      eslint-plugin-import: 2.26.0_eslint@8.15.0
+      eslint-plugin-import: 2.26.0_6nacgdzqm4zbhelsxkmd2vkvxy
     dev: true
 
   /eslint-config-airbnb-typescript/17.0.0_r46exuh3jlhq2wmrnqx2ufqspa:
@@ -3874,7 +3897,7 @@ packages:
       '@typescript-eslint/parser': 5.22.0_uhoeudlwl7kc47h4kncsfowede
       eslint: 8.12.0
       eslint-config-airbnb-base: 15.0.0_m4t3vvrby3btqwe437vnsnvyim
-      eslint-plugin-import: 2.26.0_eslint@8.12.0
+      eslint-plugin-import: 2.26.0_hhyjdrupy4c2vgtpytri6cjwoy
     dev: true
 
   /eslint-config-next/12.1.4_e6a2zi6fqdwfehht5cxvkmo3zu:
@@ -3893,13 +3916,14 @@ packages:
       eslint: 8.12.0
       eslint-import-resolver-node: 0.3.4
       eslint-import-resolver-typescript: 2.4.0_l3k33lf43msdtqtpwrwceacqke
-      eslint-plugin-import: 2.25.2_eslint@8.12.0
+      eslint-plugin-import: 2.25.2_svocbphju65ulgskrkawser2je
       eslint-plugin-jsx-a11y: 6.5.1_eslint@8.12.0
       eslint-plugin-react: 7.29.1_eslint@8.12.0
       eslint-plugin-react-hooks: 4.3.0_eslint@8.12.0
       next: 12.1.6_talmm3uuvp6ssixt2qevhfgvue
       typescript: 4.6.4
     transitivePeerDependencies:
+      - eslint-import-resolver-webpack
       - supports-color
     dev: true
 
@@ -3935,7 +3959,7 @@ packages:
     dependencies:
       debug: 4.3.4
       eslint: 8.12.0
-      eslint-plugin-import: 2.25.2_eslint@8.12.0
+      eslint-plugin-import: 2.25.2_svocbphju65ulgskrkawser2je
       glob: 7.2.0
       is-glob: 4.0.3
       resolve: 1.22.0
@@ -3944,27 +3968,69 @@ packages:
       - supports-color
     dev: true
 
-  /eslint-module-utils/2.7.3:
+  /eslint-module-utils/2.7.3_sysdrzuw2ki4kxpuwc4tznw2ha:
     resolution: {integrity: sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==}
     engines: {node: '>=4'}
+    peerDependencies:
+      '@typescript-eslint/parser': '*'
+      eslint-import-resolver-node: '*'
+      eslint-import-resolver-typescript: '*'
+      eslint-import-resolver-webpack: '*'
+    peerDependenciesMeta:
+      '@typescript-eslint/parser':
+        optional: true
+      eslint-import-resolver-node:
+        optional: true
+      eslint-import-resolver-typescript:
+        optional: true
+      eslint-import-resolver-webpack:
+        optional: true
     dependencies:
-      debug: 3.2.7
-      find-up: 2.1.0
+      '@typescript-eslint/parser': 5.10.1_uhoeudlwl7kc47h4kncsfowede
+      eslint-import-resolver-node: 0.3.6
+      eslint-import-resolver-typescript: 2.4.0_l3k33lf43msdtqtpwrwceacqke
+    dev: true
+
+  /eslint-module-utils/2.7.3_wex3ustmkv4ospy3s77r6ihlwq:
+    resolution: {integrity: sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==}
+    engines: {node: '>=4'}
+    peerDependencies:
+      '@typescript-eslint/parser': '*'
+      eslint-import-resolver-node: '*'
+      eslint-import-resolver-typescript: '*'
+      eslint-import-resolver-webpack: '*'
+    peerDependenciesMeta:
+      '@typescript-eslint/parser':
+        optional: true
+      eslint-import-resolver-node:
+        optional: true
+      eslint-import-resolver-typescript:
+        optional: true
+      eslint-import-resolver-webpack:
+        optional: true
+    dependencies:
+      '@typescript-eslint/parser': 5.22.0_uhoeudlwl7kc47h4kncsfowede
+      eslint-import-resolver-node: 0.3.6
     dev: true
 
-  /eslint-plugin-import/2.25.2_eslint@8.12.0:
+  /eslint-plugin-import/2.25.2_svocbphju65ulgskrkawser2je:
     resolution: {integrity: sha512-qCwQr9TYfoBHOFcVGKY9C9unq05uOxxdklmBXLVvcwo68y5Hta6/GzCZEMx2zQiu0woKNEER0LE7ZgaOfBU14g==}
     engines: {node: '>=4'}
     peerDependencies:
+      '@typescript-eslint/parser': '*'
       eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8
+    peerDependenciesMeta:
+      '@typescript-eslint/parser':
+        optional: true
     dependencies:
+      '@typescript-eslint/parser': 5.10.1_uhoeudlwl7kc47h4kncsfowede
       array-includes: 3.1.5
       array.prototype.flat: 1.3.0
       debug: 2.6.9
       doctrine: 2.1.0
       eslint: 8.12.0
       eslint-import-resolver-node: 0.3.6
-      eslint-module-utils: 2.7.3
+      eslint-module-utils: 2.7.3_sysdrzuw2ki4kxpuwc4tznw2ha
       has: 1.0.3
       is-core-module: 2.9.0
       is-glob: 4.0.3
@@ -3972,21 +4038,30 @@ packages:
       object.values: 1.1.5
       resolve: 1.22.0
       tsconfig-paths: 3.14.1
+    transitivePeerDependencies:
+      - eslint-import-resolver-typescript
+      - eslint-import-resolver-webpack
+      - supports-color
     dev: true
 
-  /eslint-plugin-import/2.26.0_eslint@8.12.0:
+  /eslint-plugin-import/2.26.0_6nacgdzqm4zbhelsxkmd2vkvxy:
     resolution: {integrity: sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==}
     engines: {node: '>=4'}
     peerDependencies:
+      '@typescript-eslint/parser': '*'
       eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8
+    peerDependenciesMeta:
+      '@typescript-eslint/parser':
+        optional: true
     dependencies:
+      '@typescript-eslint/parser': 5.22.0_hcfsmds2fshutdssjqluwm76uu
       array-includes: 3.1.5
       array.prototype.flat: 1.3.0
       debug: 2.6.9
       doctrine: 2.1.0
-      eslint: 8.12.0
+      eslint: 8.15.0
       eslint-import-resolver-node: 0.3.6
-      eslint-module-utils: 2.7.3
+      eslint-module-utils: 2.7.3_wex3ustmkv4ospy3s77r6ihlwq
       has: 1.0.3
       is-core-module: 2.9.0
       is-glob: 4.0.3
@@ -3994,21 +4069,30 @@ packages:
       object.values: 1.1.5
       resolve: 1.22.0
       tsconfig-paths: 3.14.1
+    transitivePeerDependencies:
+      - eslint-import-resolver-typescript
+      - eslint-import-resolver-webpack
+      - supports-color
     dev: true
 
-  /eslint-plugin-import/2.26.0_eslint@8.15.0:
+  /eslint-plugin-import/2.26.0_hhyjdrupy4c2vgtpytri6cjwoy:
     resolution: {integrity: sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==}
     engines: {node: '>=4'}
     peerDependencies:
+      '@typescript-eslint/parser': '*'
       eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8
+    peerDependenciesMeta:
+      '@typescript-eslint/parser':
+        optional: true
     dependencies:
+      '@typescript-eslint/parser': 5.22.0_uhoeudlwl7kc47h4kncsfowede
       array-includes: 3.1.5
       array.prototype.flat: 1.3.0
       debug: 2.6.9
       doctrine: 2.1.0
-      eslint: 8.15.0
+      eslint: 8.12.0
       eslint-import-resolver-node: 0.3.6
-      eslint-module-utils: 2.7.3
+      eslint-module-utils: 2.7.3_wex3ustmkv4ospy3s77r6ihlwq
       has: 1.0.3
       is-core-module: 2.9.0
       is-glob: 4.0.3
@@ -4016,6 +4100,10 @@ packages:
       object.values: 1.1.5
       resolve: 1.22.0
       tsconfig-paths: 3.14.1
+    transitivePeerDependencies:
+      - eslint-import-resolver-typescript
+      - eslint-import-resolver-webpack
+      - supports-color
     dev: true
 
   /eslint-plugin-jsx-a11y/6.5.1_eslint@8.12.0:
@@ -4422,13 +4510,6 @@ packages:
     resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==}
     dev: false
 
-  /find-up/2.1.0:
-    resolution: {integrity: sha1-RdG35QbHF93UgndaK3eSCjwMV6c=}
-    engines: {node: '>=4'}
-    dependencies:
-      locate-path: 2.0.0
-    dev: true
-
   /find-up/4.1.0:
     resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
     engines: {node: '>=8'}
@@ -5226,6 +5307,34 @@ packages:
       - ts-node
     dev: true
 
+  /jest-cli/28.1.0_@types+node@17.0.31:
+    resolution: {integrity: sha512-fDJRt6WPRriHrBsvvgb93OxgajHHsJbk4jZxiPqmZbMDRcHskfJBBfTyjFko0jjfprP544hOktdSi9HVgl4VUQ==}
+    engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0}
+    hasBin: true
+    peerDependencies:
+      node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
+    peerDependenciesMeta:
+      node-notifier:
+        optional: true
+    dependencies:
+      '@jest/core': 28.1.0
+      '@jest/test-result': 28.1.0
+      '@jest/types': 28.1.0
+      chalk: 4.1.2
+      exit: 0.1.2
+      graceful-fs: 4.2.10
+      import-local: 3.1.0
+      jest-config: 28.1.0_@types+node@17.0.31
+      jest-util: 28.1.0
+      jest-validate: 28.1.0
+      prompts: 2.4.2
+      yargs: 17.4.1
+    transitivePeerDependencies:
+      - '@types/node'
+      - supports-color
+      - ts-node
+    dev: true
+
   /jest-config/28.1.0:
     resolution: {integrity: sha512-aOV80E9LeWrmflp7hfZNn/zGA4QKv/xsn2w8QCBP0t0+YqObuCWTSgNbHJ0j9YsTuCO08ZR/wsvlxqqHX20iUA==}
     engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0}
@@ -5631,6 +5740,25 @@ packages:
       - ts-node
     dev: true
 
+  /jest/28.1.0_@types+node@17.0.31:
+    resolution: {integrity: sha512-TZR+tHxopPhzw3c3560IJXZWLNHgpcz1Zh0w5A65vynLGNcg/5pZ+VildAd7+XGOu6jd58XMY/HNn0IkZIXVXg==}
+    engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0}
+    hasBin: true
+    peerDependencies:
+      node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
+    peerDependenciesMeta:
+      node-notifier:
+        optional: true
+    dependencies:
+      '@jest/core': 28.1.0
+      import-local: 3.1.0
+      jest-cli: 28.1.0_@types+node@17.0.31
+    transitivePeerDependencies:
+      - '@types/node'
+      - supports-color
+      - ts-node
+    dev: true
+
   /js-cookie/3.0.1:
     resolution: {integrity: sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw==}
     engines: {node: '>=12'}
@@ -5787,14 +5915,6 @@ packages:
   /lines-and-columns/1.2.4:
     resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
 
-  /locate-path/2.0.0:
-    resolution: {integrity: sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=}
-    engines: {node: '>=4'}
-    dependencies:
-      p-locate: 2.0.0
-      path-exists: 3.0.0
-    dev: true
-
   /locate-path/5.0.0:
     resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
     engines: {node: '>=8'}
@@ -6289,13 +6409,6 @@ packages:
     engines: {node: '>=8.0.0'}
     dev: false
 
-  /p-limit/1.3.0:
-    resolution: {integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==}
-    engines: {node: '>=4'}
-    dependencies:
-      p-try: 1.0.0
-    dev: true
-
   /p-limit/2.3.0:
     resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
     engines: {node: '>=6'}
@@ -6303,13 +6416,6 @@ packages:
       p-try: 2.2.0
     dev: true
 
-  /p-locate/2.0.0:
-    resolution: {integrity: sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=}
-    engines: {node: '>=4'}
-    dependencies:
-      p-limit: 1.3.0
-    dev: true
-
   /p-locate/4.1.0:
     resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
     engines: {node: '>=8'}
@@ -6324,11 +6430,6 @@ packages:
       p-finally: 1.0.0
     dev: false
 
-  /p-try/1.0.0:
-    resolution: {integrity: sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=}
-    engines: {node: '>=4'}
-    dev: true
-
   /p-try/2.2.0:
     resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
     engines: {node: '>=6'}
@@ -6391,11 +6492,6 @@ packages:
       pause: 0.0.1
     dev: false
 
-  /path-exists/3.0.0:
-    resolution: {integrity: sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=}
-    engines: {node: '>=4'}
-    dev: true
-
   /path-exists/4.0.0:
     resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
     engines: {node: '>=8'}
@@ -7374,6 +7470,40 @@ packages:
       yargs-parser: 20.2.9
     dev: true
 
+  /ts-jest/28.0.2_z3fx76c5ksuwr36so7o5uc2kcy:
+    resolution: {integrity: sha512-IOZMb3D0gx6IHO9ywPgiQxJ3Zl4ECylEFwoVpENB55aTn5sdO0Ptyx/7noNBxAaUff708RqQL4XBNxxOVjY0vQ==}
+    engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0}
+    hasBin: true
+    peerDependencies:
+      '@babel/core': '>=7.0.0-beta.0 <8'
+      '@types/jest': ^27.0.0
+      babel-jest: ^28.0.0
+      esbuild: '*'
+      jest: ^28.0.0
+      typescript: '>=4.3'
+    peerDependenciesMeta:
+      '@babel/core':
+        optional: true
+      '@types/jest':
+        optional: true
+      babel-jest:
+        optional: true
+      esbuild:
+        optional: true
+    dependencies:
+      '@types/jest': 27.5.0
+      bs-logger: 0.2.6
+      fast-json-stable-stringify: 2.1.0
+      jest: 28.1.0_@types+node@17.0.31
+      jest-util: 28.1.0
+      json5: 2.2.1
+      lodash.memoize: 4.1.2
+      make-error: 1.3.6
+      semver: 7.3.7
+      typescript: 4.6.4
+      yargs-parser: 20.2.9
+    dev: true
+
   /tsconfig-paths/3.14.1:
     resolution: {integrity: sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==}
     dependencies:

+ 19 - 0
tsconfig.json

@@ -0,0 +1,19 @@
+{
+  "compilerOptions": {
+    "target": "es6",
+    "lib": ["dom", "dom.iterable", "esnext"],
+    "allowJs": true,
+    "skipLibCheck": true,
+    "strict": true,
+    "forceConsistentCasingInFileNames": true,
+    "noEmit": true,
+    "esModuleInterop": true,
+    "module": "commonjs",
+    "moduleResolution": "node",
+    "resolveJsonModule": true,
+    "isolatedModules": false,
+    "jsx": "preserve",
+    "incremental": true
+  },
+  "include": ["apps/**/*.ts"]
+}