Просмотр исходного кода

[web] Configure Babel + ESLint to handle TypeScript files (#5993)

GitOrigin-RevId: e8474274e60e5e0dcefa7263616fbaf04a9f3930
Alf Eaton 4 лет назад
Родитель
Сommit
ac0e9d06c3

+ 671 - 6
package-lock.json

@@ -1252,7 +1252,6 @@
       "version": "7.16.7",
       "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.7.tgz",
       "integrity": "sha512-YhUIJHHGkqPgEcMYkPCKTyGUdoGKWtopIycQyjJH8OjvRgOYsXsaKehLVPScKJWAULPxMa4N1vCe6szREFlZ7A==",
-      "dev": true,
       "dependencies": {
         "@babel/helper-plugin-utils": "^7.16.7"
       },
@@ -1789,7 +1788,6 @@
       "version": "7.16.8",
       "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.16.8.tgz",
       "integrity": "sha512-bHdQ9k7YpBDO2d0NVfkj51DpQcvwIzIusJ7mEUaMlbZq3Kt/U47j24inXZHQ5MDiYpCs+oZiwnXyKedE8+q7AQ==",
-      "dev": true,
       "dependencies": {
         "@babel/helper-create-class-features-plugin": "^7.16.7",
         "@babel/helper-plugin-utils": "^7.16.7",
@@ -1973,7 +1971,6 @@
       "version": "7.16.7",
       "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.16.7.tgz",
       "integrity": "sha512-WbVEmgXdIyvzB77AQjGBEyYPZx+8tTsO50XtfozQrkW8QB2rLJpH2lgx0TRw5EJrBxOZQ+wCcyPVQvS8tjEHpQ==",
-      "dev": true,
       "dependencies": {
         "@babel/helper-plugin-utils": "^7.16.7",
         "@babel/helper-validator-option": "^7.16.7",
@@ -8265,6 +8262,12 @@
       "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==",
       "dev": true
     },
+    "node_modules/@types/mocha": {
+      "version": "9.1.0",
+      "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.0.tgz",
+      "integrity": "sha512-QCWHkbMv4Y5U9oW10Uxbr45qMMSzl4OzijsozynUAgx3kEHUdXB00udx2dWDQ7f2TU2a2uuiFaRZjCe3unPpeg==",
+      "dev": true
+    },
     "node_modules/@types/mongodb": {
       "version": "3.6.20",
       "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.6.20.tgz",
@@ -8364,6 +8367,15 @@
         "csstype": "^3.0.2"
       }
     },
+    "node_modules/@types/react-bootstrap": {
+      "version": "0.32.29",
+      "resolved": "https://registry.npmjs.org/@types/react-bootstrap/-/react-bootstrap-0.32.29.tgz",
+      "integrity": "sha512-ynKNFi1lvLLOCC+/G3HMQ2hGE6dzfgCOt6S2+sQy6Y6jVYh2m5Qrc/e1OQN5B2DkUsQAtxr908OpW9lOup6Pgw==",
+      "dev": true,
+      "dependencies": {
+        "@types/react": "*"
+      }
+    },
     "node_modules/@types/react-dom": {
       "version": "17.0.11",
       "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.11.tgz",
@@ -8439,6 +8451,31 @@
         "@types/node": "*"
       }
     },
+    "node_modules/@types/sinon": {
+      "version": "10.0.11",
+      "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.11.tgz",
+      "integrity": "sha512-dmZsHlBsKUtBpHriNjlK0ndlvEh8dcb9uV9Afsbt89QIyydpC7NcR+nWlAhASfy3GHnxTl4FX/aKE7XZUt/B4g==",
+      "dev": true,
+      "dependencies": {
+        "@types/sinonjs__fake-timers": "*"
+      }
+    },
+    "node_modules/@types/sinon-chai": {
+      "version": "3.2.8",
+      "resolved": "https://registry.npmjs.org/@types/sinon-chai/-/sinon-chai-3.2.8.tgz",
+      "integrity": "sha512-d4ImIQbT/rKMG8+AXpmcan5T2/PNeSjrYhvkwet6z0p8kzYtfgA32xzOBlbU0yqJfq+/0Ml805iFoODO0LP5/g==",
+      "dev": true,
+      "dependencies": {
+        "@types/chai": "*",
+        "@types/sinon": "*"
+      }
+    },
+    "node_modules/@types/sinonjs__fake-timers": {
+      "version": "8.1.1",
+      "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz",
+      "integrity": "sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==",
+      "dev": true
+    },
     "node_modules/@types/source-list-map": {
       "version": "0.1.2",
       "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz",
@@ -8567,6 +8604,339 @@
       "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==",
       "dev": true
     },
+    "node_modules/@typescript-eslint/eslint-plugin": {
+      "version": "5.12.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.12.1.tgz",
+      "integrity": "sha512-M499lqa8rnNK7mUv74lSFFttuUsubIRdAbHcVaP93oFcKkEmHmLqy2n7jM9C8DVmFMYK61ExrZU6dLYhQZmUpw==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/scope-manager": "5.12.1",
+        "@typescript-eslint/type-utils": "5.12.1",
+        "@typescript-eslint/utils": "5.12.1",
+        "debug": "^4.3.2",
+        "functional-red-black-tree": "^1.0.1",
+        "ignore": "^5.1.8",
+        "regexpp": "^3.2.0",
+        "semver": "^7.3.5",
+        "tsutils": "^3.21.0"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependencies": {
+        "@typescript-eslint/parser": "^5.0.0",
+        "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz",
+      "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==",
+      "dev": true,
+      "engines": {
+        "node": ">= 4"
+      }
+    },
+    "node_modules/@typescript-eslint/eslint-plugin/node_modules/lru-cache": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+      "dev": true,
+      "dependencies": {
+        "yallist": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": {
+      "version": "7.3.5",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
+      "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
+      "dev": true,
+      "dependencies": {
+        "lru-cache": "^6.0.0"
+      },
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/@typescript-eslint/eslint-plugin/node_modules/yallist": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+      "dev": true
+    },
+    "node_modules/@typescript-eslint/parser": {
+      "version": "5.12.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.12.1.tgz",
+      "integrity": "sha512-6LuVUbe7oSdHxUWoX/m40Ni8gsZMKCi31rlawBHt7VtW15iHzjbpj2WLiToG2758KjtCCiLRKZqfrOdl3cNKuw==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/scope-manager": "5.12.1",
+        "@typescript-eslint/types": "5.12.1",
+        "@typescript-eslint/typescript-estree": "5.12.1",
+        "debug": "^4.3.2"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependencies": {
+        "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@typescript-eslint/scope-manager": {
+      "version": "5.12.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.12.1.tgz",
+      "integrity": "sha512-J0Wrh5xS6XNkd4TkOosxdpObzlYfXjAFIm9QxYLCPOcHVv1FyyFCPom66uIh8uBr0sZCrtS+n19tzufhwab8ZQ==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/types": "5.12.1",
+        "@typescript-eslint/visitor-keys": "5.12.1"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      }
+    },
+    "node_modules/@typescript-eslint/type-utils": {
+      "version": "5.12.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.12.1.tgz",
+      "integrity": "sha512-Gh8feEhsNLeCz6aYqynh61Vsdy+tiNNkQtc+bN3IvQvRqHkXGUhYkUi+ePKzP0Mb42se7FDb+y2SypTbpbR/Sg==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/utils": "5.12.1",
+        "debug": "^4.3.2",
+        "tsutils": "^3.21.0"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependencies": {
+        "eslint": "*"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@typescript-eslint/types": {
+      "version": "5.12.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.12.1.tgz",
+      "integrity": "sha512-hfcbq4qVOHV1YRdhkDldhV9NpmmAu2vp6wuFODL71Y0Ixak+FLeEU4rnPxgmZMnGreGEghlEucs9UZn5KOfHJA==",
+      "dev": true,
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      }
+    },
+    "node_modules/@typescript-eslint/typescript-estree": {
+      "version": "5.12.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.12.1.tgz",
+      "integrity": "sha512-ahOdkIY9Mgbza7L9sIi205Pe1inCkZWAHE1TV1bpxlU4RZNPtXaDZfiiFWcL9jdxvW1hDYZJXrFm+vlMkXRbBw==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/types": "5.12.1",
+        "@typescript-eslint/visitor-keys": "5.12.1",
+        "debug": "^4.3.2",
+        "globby": "^11.0.4",
+        "is-glob": "^4.0.3",
+        "semver": "^7.3.5",
+        "tsutils": "^3.21.0"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@typescript-eslint/typescript-estree/node_modules/array-union": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
+      "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/@typescript-eslint/typescript-estree/node_modules/globby": {
+      "version": "11.1.0",
+      "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
+      "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
+      "dev": true,
+      "dependencies": {
+        "array-union": "^2.1.0",
+        "dir-glob": "^3.0.1",
+        "fast-glob": "^3.2.9",
+        "ignore": "^5.2.0",
+        "merge2": "^1.4.1",
+        "slash": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/@typescript-eslint/typescript-estree/node_modules/ignore": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz",
+      "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==",
+      "dev": true,
+      "engines": {
+        "node": ">= 4"
+      }
+    },
+    "node_modules/@typescript-eslint/typescript-estree/node_modules/lru-cache": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+      "dev": true,
+      "dependencies": {
+        "yallist": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
+      "version": "7.3.5",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
+      "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
+      "dev": true,
+      "dependencies": {
+        "lru-cache": "^6.0.0"
+      },
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/@typescript-eslint/typescript-estree/node_modules/slash": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+      "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/@typescript-eslint/typescript-estree/node_modules/yallist": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+      "dev": true
+    },
+    "node_modules/@typescript-eslint/utils": {
+      "version": "5.12.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.12.1.tgz",
+      "integrity": "sha512-Qq9FIuU0EVEsi8fS6pG+uurbhNTtoYr4fq8tKjBupsK5Bgbk2I32UGm0Sh+WOyjOPgo/5URbxxSNV6HYsxV4MQ==",
+      "dev": true,
+      "dependencies": {
+        "@types/json-schema": "^7.0.9",
+        "@typescript-eslint/scope-manager": "5.12.1",
+        "@typescript-eslint/types": "5.12.1",
+        "@typescript-eslint/typescript-estree": "5.12.1",
+        "eslint-scope": "^5.1.1",
+        "eslint-utils": "^3.0.0"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependencies": {
+        "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
+      }
+    },
+    "node_modules/@typescript-eslint/utils/node_modules/eslint-utils": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
+      "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
+      "dev": true,
+      "dependencies": {
+        "eslint-visitor-keys": "^2.0.0"
+      },
+      "engines": {
+        "node": "^10.0.0 || ^12.0.0 || >= 14.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/mysticatea"
+      },
+      "peerDependencies": {
+        "eslint": ">=5"
+      }
+    },
+    "node_modules/@typescript-eslint/visitor-keys": {
+      "version": "5.12.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.12.1.tgz",
+      "integrity": "sha512-l1KSLfupuwrXx6wc0AuOmC7Ko5g14ZOQ86wJJqRbdLbXLK02pK/DPiDDqCc7BqqiiA04/eAA6ayL0bgOrAkH7A==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/types": "5.12.1",
+        "eslint-visitor-keys": "^3.0.0"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      }
+    },
+    "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz",
+      "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==",
+      "dev": true,
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      }
+    },
     "node_modules/@ungap/promise-all-settled": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz",
@@ -37187,6 +37557,27 @@
         "node": ">=0.6.x"
       }
     },
+    "node_modules/tsutils": {
+      "version": "3.21.0",
+      "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz",
+      "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==",
+      "dev": true,
+      "dependencies": {
+        "tslib": "^1.8.1"
+      },
+      "engines": {
+        "node": ">= 6"
+      },
+      "peerDependencies": {
+        "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta"
+      }
+    },
+    "node_modules/tsutils/node_modules/tslib": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+      "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+      "dev": true
+    },
     "node_modules/tty-browserify": {
       "version": "0.0.0",
       "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
@@ -44112,6 +44503,7 @@
         "@babel/core": "^7.14.5",
         "@babel/preset-env": "^7.14.5",
         "@babel/preset-react": "^7.14.5",
+        "@babel/preset-typescript": "^7.16.0",
         "@codemirror/autocomplete": "^0.19.13",
         "@codemirror/closebrackets": "^0.19.1",
         "@codemirror/commands": "^0.19.8",
@@ -44280,6 +44672,12 @@
         "@testing-library/dom": "^7.31.2",
         "@testing-library/react": "^11.2.7",
         "@testing-library/react-hooks": "^7.0.0",
+        "@types/chai": "^4.3.0",
+        "@types/mocha": "^9.1.0",
+        "@types/react-bootstrap": "^0.32.29",
+        "@types/sinon-chai": "^3.2.8",
+        "@typescript-eslint/eslint-plugin": "^5.12.1",
+        "@typescript-eslint/parser": "^5.3.1",
         "acorn": "^7.1.1",
         "acorn-walk": "^7.1.1",
         "angular-mocks": "~1.8.0",
@@ -44352,6 +44750,7 @@
         "terser-webpack-plugin": "^2.3.6",
         "timekeeper": "^2.2.0",
         "to-string-loader": "^1.1.6",
+        "typescript": "^4.5.5",
         "val-loader": "^1.1.1",
         "webpack": "^4.44.2",
         "webpack-assets-manifest": "^4.0.6",
@@ -46297,7 +46696,6 @@
       "version": "7.16.7",
       "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.7.tgz",
       "integrity": "sha512-YhUIJHHGkqPgEcMYkPCKTyGUdoGKWtopIycQyjJH8OjvRgOYsXsaKehLVPScKJWAULPxMa4N1vCe6szREFlZ7A==",
-      "dev": true,
       "requires": {
         "@babel/helper-plugin-utils": "^7.16.7"
       }
@@ -46618,7 +47016,6 @@
       "version": "7.16.8",
       "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.16.8.tgz",
       "integrity": "sha512-bHdQ9k7YpBDO2d0NVfkj51DpQcvwIzIusJ7mEUaMlbZq3Kt/U47j24inXZHQ5MDiYpCs+oZiwnXyKedE8+q7AQ==",
-      "dev": true,
       "requires": {
         "@babel/helper-create-class-features-plugin": "^7.16.7",
         "@babel/helper-plugin-utils": "^7.16.7",
@@ -46763,7 +47160,6 @@
       "version": "7.16.7",
       "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.16.7.tgz",
       "integrity": "sha512-WbVEmgXdIyvzB77AQjGBEyYPZx+8tTsO50XtfozQrkW8QB2rLJpH2lgx0TRw5EJrBxOZQ+wCcyPVQvS8tjEHpQ==",
-      "dev": true,
       "requires": {
         "@babel/helper-plugin-utils": "^7.16.7",
         "@babel/helper-validator-option": "^7.16.7",
@@ -51464,6 +51860,7 @@
         "@babel/core": "^7.14.5",
         "@babel/preset-env": "^7.14.5",
         "@babel/preset-react": "^7.14.5",
+        "@babel/preset-typescript": "^7.16.0",
         "@babel/register": "^7.14.5",
         "@codemirror/autocomplete": "^0.19.13",
         "@codemirror/closebrackets": "^0.19.1",
@@ -51504,6 +51901,12 @@
         "@testing-library/dom": "^7.31.2",
         "@testing-library/react": "^11.2.7",
         "@testing-library/react-hooks": "^7.0.0",
+        "@types/chai": "^4.3.0",
+        "@types/mocha": "^9.1.0",
+        "@types/react-bootstrap": "^0.32.29",
+        "@types/sinon-chai": "^3.2.8",
+        "@typescript-eslint/eslint-plugin": "^5.12.1",
+        "@typescript-eslint/parser": "^5.3.1",
         "@uppy/core": "^1.15.0",
         "@uppy/dashboard": "^1.11.0",
         "@uppy/react": "^1.11.0",
@@ -51689,6 +52092,7 @@
         "timekeeper": "^2.2.0",
         "to-string-loader": "^1.1.6",
         "tsscmp": "^1.0.6",
+        "typescript": "^4.5.5",
         "underscore": "^1.13.1",
         "unzipper": "^0.10.11",
         "url-parse": "^1.4.7",
@@ -55541,6 +55945,12 @@
       "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==",
       "dev": true
     },
+    "@types/mocha": {
+      "version": "9.1.0",
+      "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.0.tgz",
+      "integrity": "sha512-QCWHkbMv4Y5U9oW10Uxbr45qMMSzl4OzijsozynUAgx3kEHUdXB00udx2dWDQ7f2TU2a2uuiFaRZjCe3unPpeg==",
+      "dev": true
+    },
     "@types/mongodb": {
       "version": "3.6.20",
       "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.6.20.tgz",
@@ -55647,6 +56057,15 @@
         }
       }
     },
+    "@types/react-bootstrap": {
+      "version": "0.32.29",
+      "resolved": "https://registry.npmjs.org/@types/react-bootstrap/-/react-bootstrap-0.32.29.tgz",
+      "integrity": "sha512-ynKNFi1lvLLOCC+/G3HMQ2hGE6dzfgCOt6S2+sQy6Y6jVYh2m5Qrc/e1OQN5B2DkUsQAtxr908OpW9lOup6Pgw==",
+      "dev": true,
+      "requires": {
+        "@types/react": "*"
+      }
+    },
     "@types/react-dom": {
       "version": "17.0.11",
       "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.11.tgz",
@@ -55716,6 +56135,31 @@
         "@types/node": "*"
       }
     },
+    "@types/sinon": {
+      "version": "10.0.11",
+      "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.11.tgz",
+      "integrity": "sha512-dmZsHlBsKUtBpHriNjlK0ndlvEh8dcb9uV9Afsbt89QIyydpC7NcR+nWlAhASfy3GHnxTl4FX/aKE7XZUt/B4g==",
+      "dev": true,
+      "requires": {
+        "@types/sinonjs__fake-timers": "*"
+      }
+    },
+    "@types/sinon-chai": {
+      "version": "3.2.8",
+      "resolved": "https://registry.npmjs.org/@types/sinon-chai/-/sinon-chai-3.2.8.tgz",
+      "integrity": "sha512-d4ImIQbT/rKMG8+AXpmcan5T2/PNeSjrYhvkwet6z0p8kzYtfgA32xzOBlbU0yqJfq+/0Ml805iFoODO0LP5/g==",
+      "dev": true,
+      "requires": {
+        "@types/chai": "*",
+        "@types/sinon": "*"
+      }
+    },
+    "@types/sinonjs__fake-timers": {
+      "version": "8.1.1",
+      "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz",
+      "integrity": "sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==",
+      "dev": true
+    },
     "@types/source-list-map": {
       "version": "0.1.2",
       "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz",
@@ -55841,6 +56285,210 @@
       "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==",
       "dev": true
     },
+    "@typescript-eslint/eslint-plugin": {
+      "version": "5.12.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.12.1.tgz",
+      "integrity": "sha512-M499lqa8rnNK7mUv74lSFFttuUsubIRdAbHcVaP93oFcKkEmHmLqy2n7jM9C8DVmFMYK61ExrZU6dLYhQZmUpw==",
+      "dev": true,
+      "requires": {
+        "@typescript-eslint/scope-manager": "5.12.1",
+        "@typescript-eslint/type-utils": "5.12.1",
+        "@typescript-eslint/utils": "5.12.1",
+        "debug": "^4.3.2",
+        "functional-red-black-tree": "^1.0.1",
+        "ignore": "^5.1.8",
+        "regexpp": "^3.2.0",
+        "semver": "^7.3.5",
+        "tsutils": "^3.21.0"
+      },
+      "dependencies": {
+        "ignore": {
+          "version": "5.2.0",
+          "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz",
+          "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==",
+          "dev": true
+        },
+        "lru-cache": {
+          "version": "6.0.0",
+          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+          "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+          "dev": true,
+          "requires": {
+            "yallist": "^4.0.0"
+          }
+        },
+        "semver": {
+          "version": "7.3.5",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
+          "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
+          "dev": true,
+          "requires": {
+            "lru-cache": "^6.0.0"
+          }
+        },
+        "yallist": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+          "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+          "dev": true
+        }
+      }
+    },
+    "@typescript-eslint/parser": {
+      "version": "5.12.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.12.1.tgz",
+      "integrity": "sha512-6LuVUbe7oSdHxUWoX/m40Ni8gsZMKCi31rlawBHt7VtW15iHzjbpj2WLiToG2758KjtCCiLRKZqfrOdl3cNKuw==",
+      "dev": true,
+      "requires": {
+        "@typescript-eslint/scope-manager": "5.12.1",
+        "@typescript-eslint/types": "5.12.1",
+        "@typescript-eslint/typescript-estree": "5.12.1",
+        "debug": "^4.3.2"
+      }
+    },
+    "@typescript-eslint/scope-manager": {
+      "version": "5.12.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.12.1.tgz",
+      "integrity": "sha512-J0Wrh5xS6XNkd4TkOosxdpObzlYfXjAFIm9QxYLCPOcHVv1FyyFCPom66uIh8uBr0sZCrtS+n19tzufhwab8ZQ==",
+      "dev": true,
+      "requires": {
+        "@typescript-eslint/types": "5.12.1",
+        "@typescript-eslint/visitor-keys": "5.12.1"
+      }
+    },
+    "@typescript-eslint/type-utils": {
+      "version": "5.12.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.12.1.tgz",
+      "integrity": "sha512-Gh8feEhsNLeCz6aYqynh61Vsdy+tiNNkQtc+bN3IvQvRqHkXGUhYkUi+ePKzP0Mb42se7FDb+y2SypTbpbR/Sg==",
+      "dev": true,
+      "requires": {
+        "@typescript-eslint/utils": "5.12.1",
+        "debug": "^4.3.2",
+        "tsutils": "^3.21.0"
+      }
+    },
+    "@typescript-eslint/types": {
+      "version": "5.12.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.12.1.tgz",
+      "integrity": "sha512-hfcbq4qVOHV1YRdhkDldhV9NpmmAu2vp6wuFODL71Y0Ixak+FLeEU4rnPxgmZMnGreGEghlEucs9UZn5KOfHJA==",
+      "dev": true
+    },
+    "@typescript-eslint/typescript-estree": {
+      "version": "5.12.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.12.1.tgz",
+      "integrity": "sha512-ahOdkIY9Mgbza7L9sIi205Pe1inCkZWAHE1TV1bpxlU4RZNPtXaDZfiiFWcL9jdxvW1hDYZJXrFm+vlMkXRbBw==",
+      "dev": true,
+      "requires": {
+        "@typescript-eslint/types": "5.12.1",
+        "@typescript-eslint/visitor-keys": "5.12.1",
+        "debug": "^4.3.2",
+        "globby": "^11.0.4",
+        "is-glob": "^4.0.3",
+        "semver": "^7.3.5",
+        "tsutils": "^3.21.0"
+      },
+      "dependencies": {
+        "array-union": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
+          "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+          "dev": true
+        },
+        "globby": {
+          "version": "11.1.0",
+          "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
+          "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
+          "dev": true,
+          "requires": {
+            "array-union": "^2.1.0",
+            "dir-glob": "^3.0.1",
+            "fast-glob": "^3.2.9",
+            "ignore": "^5.2.0",
+            "merge2": "^1.4.1",
+            "slash": "^3.0.0"
+          }
+        },
+        "ignore": {
+          "version": "5.2.0",
+          "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz",
+          "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==",
+          "dev": true
+        },
+        "lru-cache": {
+          "version": "6.0.0",
+          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+          "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+          "dev": true,
+          "requires": {
+            "yallist": "^4.0.0"
+          }
+        },
+        "semver": {
+          "version": "7.3.5",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
+          "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
+          "dev": true,
+          "requires": {
+            "lru-cache": "^6.0.0"
+          }
+        },
+        "slash": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+          "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+          "dev": true
+        },
+        "yallist": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+          "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+          "dev": true
+        }
+      }
+    },
+    "@typescript-eslint/utils": {
+      "version": "5.12.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.12.1.tgz",
+      "integrity": "sha512-Qq9FIuU0EVEsi8fS6pG+uurbhNTtoYr4fq8tKjBupsK5Bgbk2I32UGm0Sh+WOyjOPgo/5URbxxSNV6HYsxV4MQ==",
+      "dev": true,
+      "requires": {
+        "@types/json-schema": "^7.0.9",
+        "@typescript-eslint/scope-manager": "5.12.1",
+        "@typescript-eslint/types": "5.12.1",
+        "@typescript-eslint/typescript-estree": "5.12.1",
+        "eslint-scope": "^5.1.1",
+        "eslint-utils": "^3.0.0"
+      },
+      "dependencies": {
+        "eslint-utils": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
+          "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
+          "dev": true,
+          "requires": {
+            "eslint-visitor-keys": "^2.0.0"
+          }
+        }
+      }
+    },
+    "@typescript-eslint/visitor-keys": {
+      "version": "5.12.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.12.1.tgz",
+      "integrity": "sha512-l1KSLfupuwrXx6wc0AuOmC7Ko5g14ZOQ86wJJqRbdLbXLK02pK/DPiDDqCc7BqqiiA04/eAA6ayL0bgOrAkH7A==",
+      "dev": true,
+      "requires": {
+        "@typescript-eslint/types": "5.12.1",
+        "eslint-visitor-keys": "^3.0.0"
+      },
+      "dependencies": {
+        "eslint-visitor-keys": {
+          "version": "3.3.0",
+          "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz",
+          "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==",
+          "dev": true
+        }
+      }
+    },
     "@ungap/promise-all-settled": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz",
@@ -78901,6 +79549,23 @@
       "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz",
       "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA=="
     },
+    "tsutils": {
+      "version": "3.21.0",
+      "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz",
+      "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==",
+      "dev": true,
+      "requires": {
+        "tslib": "^1.8.1"
+      },
+      "dependencies": {
+        "tslib": {
+          "version": "1.14.1",
+          "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+          "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+          "dev": true
+        }
+      }
+    },
     "tty-browserify": {
       "version": "0.0.0",
       "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",

+ 25 - 1
services/web/.eslintrc

@@ -1,7 +1,9 @@
 {
   "root": true,
+  "parser": "@typescript-eslint/parser",
   "extends": [
     "eslint:recommended",
+    "plugin:@typescript-eslint/recommended",
     "standard",
     "prettier"
   ],
@@ -16,7 +18,14 @@
   },
   "rules": {
     // do not allow importing of implicit dependencies.
-    "import/no-extraneous-dependencies": "error"
+    "import/no-extraneous-dependencies": "error",
+
+    // disable some TypeScript rules
+    "@typescript-eslint/no-var-requires": "off",
+    "@typescript-eslint/no-unused-vars": "off",
+    "@typescript-eslint/no-empty-function": "off",
+    "@typescript-eslint/no-explicit-any": "off",
+    "@typescript-eslint/no-this-alias": "off"
   },
   "overrides": [
     // NOTE: changing paths may require updating them in the Makefile too.
@@ -180,6 +189,21 @@
         ]
       }
     },
+    // React + TypeScript-specific rules
+    {
+      "files": ["**/*.tsx"],
+      "rules": {
+        "react/prop-types": "off",
+        "no-undef": "off"
+      }
+    },
+    // TypeScript-specific rules
+    {
+      "files": ["**/*.ts"],
+      "rules": {
+        "no-undef": "off"
+      }
+    },
     {
       "files": ["scripts/ukamf/*.js"],
       "rules": {

+ 2 - 0
services/web/babel.config.json

@@ -1,5 +1,6 @@
 {
   "presets": [
+    "@babel/typescript",
     [
       "@babel/react",
       {
@@ -21,6 +22,7 @@
   "env": {
     "test": {
       "presets": [
+        "@babel/typescript",
         [
           "@babel/react",
           {

+ 9 - 1
services/web/package.json

@@ -44,6 +44,7 @@
     "@babel/core": "^7.14.5",
     "@babel/preset-env": "^7.14.5",
     "@babel/preset-react": "^7.14.5",
+    "@babel/preset-typescript": "^7.16.0",
     "@codemirror/autocomplete": "^0.19.13",
     "@codemirror/closebrackets": "^0.19.1",
     "@codemirror/commands": "^0.19.8",
@@ -212,6 +213,12 @@
     "@testing-library/dom": "^7.31.2",
     "@testing-library/react": "^11.2.7",
     "@testing-library/react-hooks": "^7.0.0",
+    "@types/chai": "^4.3.0",
+    "@types/mocha": "^9.1.0",
+    "@types/react-bootstrap": "^0.32.29",
+    "@types/sinon-chai": "^3.2.8",
+    "@typescript-eslint/eslint-plugin": "^5.12.1",
+    "@typescript-eslint/parser": "^5.3.1",
     "acorn": "^7.1.1",
     "acorn-walk": "^7.1.1",
     "angular-mocks": "~1.8.0",
@@ -230,7 +237,7 @@
     "css-loader": "^3.5.2",
     "es6-promise": "^4.2.8",
     "escodegen": "^2.0.0",
-    "eslint": "^8.8.0",
+    "eslint": "^8.9.0",
     "eslint-config-prettier": "^8.3.0",
     "eslint-config-standard": "^16.0.3",
     "eslint-config-standard-jsx": "^11.0.0-0",
@@ -284,6 +291,7 @@
     "terser-webpack-plugin": "^2.3.6",
     "timekeeper": "^2.2.0",
     "to-string-loader": "^1.1.6",
+    "typescript": "^4.5.5",
     "val-loader": "^1.1.1",
     "webpack": "^4.44.2",
     "webpack-assets-manifest": "^4.0.6",

+ 3 - 1
services/web/test/frontend/bootstrap.js

@@ -1,5 +1,7 @@
 // Run babel on tests to allow support for import/export statements in Node
-require('@babel/register')
+require('@babel/register')({
+  extensions: ['.ts', '.tsx', '.js', '.jsx'],
+})
 
 // Load JSDOM to mock the DOM in Node
 // Set pretendToBeVisual to enable requestAnimationFrame

+ 16 - 0
services/web/tsconfig.json

@@ -0,0 +1,16 @@
+{
+  "compilerOptions": {
+    "target": "esnext" /* Specify ECMAScript target version */,
+    "module": "esnext" /* Specify module code generation */,
+    "allowJs": true /* Allow JavaScript files to be compiled. */,
+    //    "checkJs": true /* Report errors in .js files. */,
+    "jsx": "preserve" /* Specify JSX code generation */,
+    "noEmit": true /* Do not emit outputs. */,
+    //    "strict": true /* Enable all strict type-checking options. */,
+    "moduleResolution": "node" /* Specify module resolution strategy */,
+    "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
+    "skipLibCheck": true /* Skip type checking of declaration files. */,
+    "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
+  },
+  "include": ["frontend/js/**/*.*", "modules/**/frontend/js/**/*.*"]
+}

+ 3 - 2
services/web/webpack.config.js

@@ -81,8 +81,8 @@ module.exports = {
   module: {
     rules: [
       {
-        // Pass application JS files through babel-loader, compiling to ES5
-        test: /\.js$/,
+        // Pass application JS/TS files through babel-loader, compiling to ES5
+        test: /\.[j|t]sx?$/,
         // Only compile application files (npm and vendored dependencies are in
         // ES5 already)
         exclude: [
@@ -278,6 +278,7 @@ module.exports = {
       ),
     },
     symlinks: false,
+    extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'],
   },
 
   // Split out files into separate (derived) bundles if they are shared between