diff --git a/.husky/.gitattributes b/.husky/.gitattributes new file mode 100644 index 00000000..27a4bbb0 --- /dev/null +++ b/.husky/.gitattributes @@ -0,0 +1,3 @@ +# Enforce Unix newlines +.gitattributes text eol=lf +pre-commit text eol=lf diff --git a/package-lock.json b/package-lock.json index 7eb1aa1c..8ff821f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,8 @@ "@typescript-eslint/parser": "^5.15.0", "eslint": "^8.11.0", "eslint-config-prettier": "^8.5.0", + "eslint-import-resolver-typescript": "^2.5.0", + "eslint-plugin-import": "^2.25.4", "eslint-plugin-prettier": "^4.0.0", "husky": "^7.0.4", "lerna": "^4.0.0", @@ -1480,6 +1482,12 @@ "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", "dev": true }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", + "dev": true + }, "node_modules/@types/minimatch": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", @@ -1894,6 +1902,25 @@ "integrity": "sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4=", "dev": true }, + "node_modules/array-includes": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.4.tgz", + "integrity": "sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1", + "get-intrinsic": "^1.1.1", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -1903,6 +1930,23 @@ "node": ">=8" } }, + "node_modules/array.prototype.flat": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz", + "integrity": "sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", @@ -3058,6 +3102,188 @@ "eslint": ">=7.0.0" } }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", + "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "resolve": "^1.20.0" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-2.5.0.tgz", + "integrity": "sha512-qZ6e5CFr+I7K4VVhQu3M/9xGv9/YmwsEXrsm3nimw8vWaVHRDrQRp26BgCypTxBp3vUp4o5aVEJRiy0F2DFddQ==", + "dev": true, + "dependencies": { + "debug": "^4.3.1", + "glob": "^7.1.7", + "is-glob": "^4.0.1", + "resolve": "^1.20.0", + "tsconfig-paths": "^3.9.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz", + "integrity": "sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "find-up": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils/node_modules/find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "dependencies": { + "locate-path": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-module-utils/node_modules/locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "dependencies": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-module-utils/node_modules/p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "dependencies": { + "p-try": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-module-utils/node_modules/p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "dependencies": { + "p-limit": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-module-utils/node_modules/p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-module-utils/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.25.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.25.4.tgz", + "integrity": "sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.4", + "array.prototype.flat": "^1.2.5", + "debug": "^2.6.9", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-module-utils": "^2.7.2", + "has": "^1.0.3", + "is-core-module": "^2.8.0", + "is-glob": "^4.0.3", + "minimatch": "^3.0.4", + "object.values": "^1.1.5", + "resolve": "^1.20.0", + "tsconfig-paths": "^3.12.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, "node_modules/eslint-plugin-prettier": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz", @@ -4643,6 +4869,18 @@ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", "dev": true }, + "node_modules/json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -6095,6 +6333,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object.values": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", + "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -7830,6 +8085,27 @@ "node": ">=8" } }, + "node_modules/tsconfig-paths": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.0.tgz", + "integrity": "sha512-cg/1jAZoL57R39+wiw4u/SCC6Ic9Q5NqjBOb+9xISedOYurfog9ZNmKJSxAnb2m/5Bq4lE9lhUcau33Ml8DM0g==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.0", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -9606,6 +9882,12 @@ "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", "dev": true }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", + "dev": true + }, "@types/minimatch": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", @@ -9890,12 +10172,36 @@ "integrity": "sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4=", "dev": true }, + "array-includes": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.4.tgz", + "integrity": "sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1", + "get-intrinsic": "^1.1.1", + "is-string": "^1.0.7" + } + }, "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 }, + "array.prototype.flat": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz", + "integrity": "sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0" + } + }, "arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", @@ -10822,6 +11128,157 @@ "dev": true, "requires": {} }, + "eslint-import-resolver-node": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", + "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", + "dev": true, + "requires": { + "debug": "^3.2.7", + "resolve": "^1.20.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "eslint-import-resolver-typescript": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-2.5.0.tgz", + "integrity": "sha512-qZ6e5CFr+I7K4VVhQu3M/9xGv9/YmwsEXrsm3nimw8vWaVHRDrQRp26BgCypTxBp3vUp4o5aVEJRiy0F2DFddQ==", + "dev": true, + "requires": { + "debug": "^4.3.1", + "glob": "^7.1.7", + "is-glob": "^4.0.1", + "resolve": "^1.20.0", + "tsconfig-paths": "^3.9.0" + } + }, + "eslint-module-utils": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz", + "integrity": "sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==", + "dev": true, + "requires": { + "debug": "^3.2.7", + "find-up": "^2.1.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + } + } + }, + "eslint-plugin-import": { + "version": "2.25.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.25.4.tgz", + "integrity": "sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA==", + "dev": true, + "requires": { + "array-includes": "^3.1.4", + "array.prototype.flat": "^1.2.5", + "debug": "^2.6.9", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-module-utils": "^2.7.2", + "has": "^1.0.3", + "is-core-module": "^2.8.0", + "is-glob": "^4.0.3", + "minimatch": "^3.0.4", + "object.values": "^1.1.5", + "resolve": "^1.20.0", + "tsconfig-paths": "^3.12.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, "eslint-plugin-prettier": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz", @@ -12011,6 +12468,15 @@ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", "dev": true }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, "jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -13156,6 +13622,17 @@ "es-abstract": "^1.19.1" } }, + "object.values": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", + "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + } + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -14448,6 +14925,26 @@ "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", "dev": true }, + "tsconfig-paths": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.0.tgz", + "integrity": "sha512-cg/1jAZoL57R39+wiw4u/SCC6Ic9Q5NqjBOb+9xISedOYurfog9ZNmKJSxAnb2m/5Bq4lE9lhUcau33Ml8DM0g==", + "dev": true, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + } + } + }, "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", diff --git a/package.json b/package.json index 28027fa6..f9b66ed1 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,8 @@ "@typescript-eslint/parser": "^5.15.0", "eslint": "^8.11.0", "eslint-config-prettier": "^8.5.0", + "eslint-import-resolver-typescript": "^2.5.0", + "eslint-plugin-import": "^2.25.4", "eslint-plugin-prettier": "^4.0.0", "husky": "^7.0.4", "lerna": "^4.0.0", diff --git a/packages/backend/.eslintrc.js b/packages/backend/.eslintrc.js index 2958b711..087b92f5 100644 --- a/packages/backend/.eslintrc.js +++ b/packages/backend/.eslintrc.js @@ -1,10 +1,17 @@ -const path = require('node:path'); +const path = require('path'); +const project = path.resolve(__dirname, './tsconfig.json'); module.exports = { + ignorePatterns: ['.*'], parser: '@typescript-eslint/parser', - parserOptions: { project: path.resolve(__dirname, './tsconfig.json'), sourceType: 'module' }, - plugins: ['@typescript-eslint/eslint-plugin'], - extends: ['plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended'], + parserOptions: { project, sourceType: 'module' }, + plugins: ['@typescript-eslint/eslint-plugin', 'import'], + extends: [ + 'plugin:@typescript-eslint/recommended', + 'plugin:prettier/recommended', + 'plugin:import/recommended', + 'plugin:import/typescript', + ], root: true, env: { node: true, jest: true }, rules: { @@ -13,7 +20,15 @@ module.exports = { '@typescript-eslint/no-inferrable-types': ['warn', { ignoreParameters: true }], 'no-mixed-operators': 'error', '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_+', varsIgnorePattern: '^_+' }], + 'import/order': ['warn', { alphabetize: { order: 'asc', caseInsensitive: true }, 'newlines-between': 'never' }], 'prefer-destructuring': 'warn', 'prefer-template': 'warn', }, + settings: { + 'import/resolver': { + typescript: { + project, + }, + }, + }, }; diff --git a/packages/backend/example.env b/packages/backend/example.env index 553a9276..3edf3de8 100644 --- a/packages/backend/example.env +++ b/packages/backend/example.env @@ -1,4 +1,4 @@ PORT=9001 +SSL_PORT=9002 SSL_CERT_FILE=fullchain.pem SSL_KEY_FILE=privkey.pem -USE_SSL=false diff --git a/packages/backend/package-lock.json b/packages/backend/package-lock.json index 4c9c4425..a4e05d9e 100644 --- a/packages/backend/package-lock.json +++ b/packages/backend/package-lock.json @@ -7,12 +7,15 @@ "name": "backend", "dependencies": { "@nestjs/common": "^8.4.1", + "@nestjs/config": "^1.2.0", "@nestjs/core": "^8.4.1", "@nestjs/platform-express": "^8.4.1", "chalk": "^4.1.2", + "dayjs": "^1.11.0", "dotenv": "^16.0.0", "envalid": "^7.3.0", "fast-safe-stringify": "^2.1.1", + "joi": "^17.6.0", "mongoose": "^6.2.6", "node-graceful": "^3.1.0", "reflect-metadata": "^0.1.13", @@ -829,6 +832,19 @@ "kuler": "^2.0.0" } }, + "node_modules/@hapi/hoek": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.2.1.tgz", + "integrity": "sha512-gfta+H8aziZsm8pZa0vj04KO6biEiisppNgA1kbJvFrrWu9Vm7eaUEy76DIxsuTaWvti5fkJVhllWc6ZTE+Mdw==" + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -1394,6 +1410,22 @@ } } }, + "node_modules/@nestjs/config": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-1.2.0.tgz", + "integrity": "sha512-GGZOj2g6EMZ23orsQqeD2Vs5E2ZrmAiB0qCGvERv+5nQmZjY4nKkisG4awQsym1uotmmzgtsd9lOiKqTIFONhA==", + "dependencies": { + "dotenv": "16.0.0", + "dotenv-expand": "8.0.1", + "lodash": "4.17.21", + "uuid": "8.3.2" + }, + "peerDependencies": { + "@nestjs/common": "^7.0.0 || ^8.0.0", + "reflect-metadata": "^0.1.13", + "rxjs": "^6.0.0 || ^7.2.0" + } + }, "node_modules/@nestjs/core": { "version": "8.4.1", "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-8.4.1.tgz", @@ -1513,6 +1545,24 @@ "npm": ">=5.0.0" } }, + "node_modules/@sideway/address": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.3.tgz", + "integrity": "sha512-8ncEUtmnTsMmL7z1YPB47kPUq7LpKWJNFPsRzHiIajGC5uXlWGn+AmkYPcHNl8S4tcEGx+cnORnNYaw2wvL+LQ==", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.0.tgz", + "integrity": "sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==" + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" + }, "node_modules/@sinonjs/commons": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", @@ -2660,6 +2710,24 @@ "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", "dev": true }, + "node_modules/class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", + "optional": true, + "peer": true + }, + "node_modules/class-validator": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.13.2.tgz", + "integrity": "sha512-yBUcQy07FPlGzUjoLuUfIOXzgynnQPPruyK1Ge2B74k9ROwnle1E+NxLWnUv5OLU8hA/qL5leAE9XnXq3byaBw==", + "optional": true, + "peer": true, + "dependencies": { + "libphonenumber-js": "^1.9.43", + "validator": "^13.7.0" + } + }, "node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -3037,6 +3105,11 @@ "node": ">=10" } }, + "node_modules/dayjs": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.0.tgz", + "integrity": "sha512-JLC809s6Y948/FuCZPm5IX8rRhQwOiyMb2TfVVQEixG7P8Lm/gt5S7yoQZmC8x1UehI9Pb7sksEt4xx14m+7Ug==" + }, "node_modules/debug": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", @@ -3197,6 +3270,14 @@ "node": ">=12" } }, + "node_modules/dotenv-expand": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-8.0.1.tgz", + "integrity": "sha512-j/Ih7bIERDR5PzI89Zu8ayd3tXZ6E3dbY0ljQ9Db0K87qBO8zdLsi2dIvDHMWtjC3Yxb8XixOTHAtia0fDHRpg==", + "engines": { + "node": ">=12" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -5083,6 +5164,18 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/joi": { + "version": "17.6.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.6.0.tgz", + "integrity": "sha512-OX5dG6DTbcr/kbMFj0KGYxuew69HPcAE3K/sZpEV2nP6e/j/C0HV+HNiBPCASxdx5T7DMoa0s8UeHWMnb6n2zw==", + "dependencies": { + "@hapi/hoek": "^9.0.0", + "@hapi/topo": "^5.0.0", + "@sideway/address": "^4.1.3", + "@sideway/formula": "^3.0.0", + "@sideway/pinpoint": "^2.0.0" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -5251,6 +5344,13 @@ "node": ">=6" } }, + "node_modules/libphonenumber-js": { + "version": "1.9.49", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.9.49.tgz", + "integrity": "sha512-/wEOIONcVboFky+lWlCaF7glm1FhBz11M5PHeCApA+xDdVfmhKjHktHS8KjyGxouV5CSXIr4f3GvLSpJa4qMSg==", + "optional": true, + "peer": true + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -5281,8 +5381,7 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/lodash.memoize": { "version": "4.1.2", @@ -7506,6 +7605,16 @@ "node": ">=10.12.0" } }, + "node_modules/validator": { + "version": "13.7.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz", + "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==", + "optional": true, + "peer": true, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -8676,6 +8785,19 @@ "kuler": "^2.0.0" } }, + "@hapi/hoek": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.2.1.tgz", + "integrity": "sha512-gfta+H8aziZsm8pZa0vj04KO6biEiisppNgA1kbJvFrrWu9Vm7eaUEy76DIxsuTaWvti5fkJVhllWc6ZTE+Mdw==" + }, + "@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -9107,6 +9229,17 @@ "uuid": "8.3.2" } }, + "@nestjs/config": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-1.2.0.tgz", + "integrity": "sha512-GGZOj2g6EMZ23orsQqeD2Vs5E2ZrmAiB0qCGvERv+5nQmZjY4nKkisG4awQsym1uotmmzgtsd9lOiKqTIFONhA==", + "requires": { + "dotenv": "16.0.0", + "dotenv-expand": "8.0.1", + "lodash": "4.17.21", + "uuid": "8.3.2" + } + }, "@nestjs/core": { "version": "8.4.1", "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-8.4.1.tgz", @@ -9166,6 +9299,24 @@ "node-fetch": "^2.6.1" } }, + "@sideway/address": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.3.tgz", + "integrity": "sha512-8ncEUtmnTsMmL7z1YPB47kPUq7LpKWJNFPsRzHiIajGC5uXlWGn+AmkYPcHNl8S4tcEGx+cnORnNYaw2wvL+LQ==", + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, + "@sideway/formula": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.0.tgz", + "integrity": "sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==" + }, + "@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" + }, "@sinonjs/commons": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", @@ -10140,6 +10291,24 @@ "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", "dev": true }, + "class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", + "optional": true, + "peer": true + }, + "class-validator": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.13.2.tgz", + "integrity": "sha512-yBUcQy07FPlGzUjoLuUfIOXzgynnQPPruyK1Ge2B74k9ROwnle1E+NxLWnUv5OLU8hA/qL5leAE9XnXq3byaBw==", + "optional": true, + "peer": true, + "requires": { + "libphonenumber-js": "^1.9.43", + "validator": "^13.7.0" + } + }, "cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -10462,6 +10631,11 @@ "whatwg-url": "^8.0.0" } }, + "dayjs": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.0.tgz", + "integrity": "sha512-JLC809s6Y948/FuCZPm5IX8rRhQwOiyMb2TfVVQEixG7P8Lm/gt5S7yoQZmC8x1UehI9Pb7sksEt4xx14m+7Ug==" + }, "debug": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", @@ -10583,6 +10757,11 @@ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.0.tgz", "integrity": "sha512-qD9WU0MPM4SWLPJy/r2Be+2WgQj8plChsyrCNQzW/0WjvcJQiKQJ9mH3ZgB3fxbUUxgc/11ZJ0Fi5KiimWGz2Q==" }, + "dotenv-expand": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-8.0.1.tgz", + "integrity": "sha512-j/Ih7bIERDR5PzI89Zu8ayd3tXZ6E3dbY0ljQ9Db0K87qBO8zdLsi2dIvDHMWtjC3Yxb8XixOTHAtia0fDHRpg==" + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -12029,6 +12208,18 @@ } } }, + "joi": { + "version": "17.6.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.6.0.tgz", + "integrity": "sha512-OX5dG6DTbcr/kbMFj0KGYxuew69HPcAE3K/sZpEV2nP6e/j/C0HV+HNiBPCASxdx5T7DMoa0s8UeHWMnb6n2zw==", + "requires": { + "@hapi/hoek": "^9.0.0", + "@hapi/topo": "^5.0.0", + "@sideway/address": "^4.1.3", + "@sideway/formula": "^3.0.0", + "@sideway/pinpoint": "^2.0.0" + } + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -12159,6 +12350,13 @@ "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", "dev": true }, + "libphonenumber-js": { + "version": "1.9.49", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.9.49.tgz", + "integrity": "sha512-/wEOIONcVboFky+lWlCaF7glm1FhBz11M5PHeCApA+xDdVfmhKjHktHS8KjyGxouV5CSXIr4f3GvLSpJa4qMSg==", + "optional": true, + "peer": true + }, "lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -12183,8 +12381,7 @@ "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "lodash.memoize": { "version": "4.1.2", @@ -13842,6 +14039,13 @@ "source-map": "^0.7.3" } }, + "validator": { + "version": "13.7.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz", + "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==", + "optional": true, + "peer": true + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/packages/backend/package.json b/packages/backend/package.json index 67789b1c..3df28e8d 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -9,7 +9,7 @@ "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", - "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", + "lint": "eslint \"{src,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", @@ -18,12 +18,15 @@ }, "dependencies": { "@nestjs/common": "^8.4.1", + "@nestjs/config": "^1.2.0", "@nestjs/core": "^8.4.1", "@nestjs/platform-express": "^8.4.1", "chalk": "^4.1.2", + "dayjs": "^1.11.0", "dotenv": "^16.0.0", "envalid": "^7.3.0", "fast-safe-stringify": "^2.1.1", + "joi": "^17.6.0", "mongoose": "^6.2.6", "node-graceful": "^3.1.0", "reflect-metadata": "^0.1.13", diff --git a/packages/backend/src/app.module.ts b/packages/backend/src/app.module.ts index 86628031..267b745c 100644 --- a/packages/backend/src/app.module.ts +++ b/packages/backend/src/app.module.ts @@ -1,10 +1,18 @@ import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; import { AppController } from './app.controller'; import { AppService } from './app.service'; +import { expressConfig, requireConfig } from '~/config'; +import { LoggerService } from '~/logger/service'; @Module({ - imports: [], + imports: [ + ConfigModule.forRoot({ + // expressConfig is required in main.ts + validate: requireConfig(expressConfig), + }), + ], controllers: [AppController], - providers: [AppService], + providers: [AppService, LoggerService], }) export class AppModule {} diff --git a/packages/backend/src/app.service.ts b/packages/backend/src/app.service.ts index 927d7cca..ca409b6e 100644 --- a/packages/backend/src/app.service.ts +++ b/packages/backend/src/app.service.ts @@ -1,7 +1,12 @@ import { Injectable } from '@nestjs/common'; +import { LoggerService } from '~/logger/service'; @Injectable() export class AppService { + constructor(private logger: LoggerService) { + this.logger.setContext(AppService.name); + } + getHello(): string { return 'Hello World!'; } diff --git a/packages/backend/src/config/express.ts b/packages/backend/src/config/express.ts new file mode 100644 index 00000000..070828f6 --- /dev/null +++ b/packages/backend/src/config/express.ts @@ -0,0 +1,19 @@ +import Joi from 'joi'; + +export interface ExpressConfig { + PORT?: number; + SSL_PORT?: number; + SSL_CERT_FILE: string; + SSL_KEY_FILE: string; +} + +export const expressConfig = Joi.object({ + PORT: Joi.number() + .port() + // Defaults to port 9001 when no port is specified for HTTP or HTTPS. + .when('SSL_PORT', { not: Joi.exist(), then: Joi.number().default(9001) }), + SSL_PORT: Joi.number().port(), + SSL_CERT_FILE: Joi.string(), + SSL_KEY_FILE: Joi.string(), +}) // + .with('SSL_PORT', ['SSL_CERT_FILE', 'SSL_KEY_FILE']); diff --git a/packages/backend/src/config/index.ts b/packages/backend/src/config/index.ts new file mode 100644 index 00000000..5ccbf08a --- /dev/null +++ b/packages/backend/src/config/index.ts @@ -0,0 +1,19 @@ +import { ObjectSchema } from 'joi'; +export * from './database'; +export * from './express'; + +export function requireConfig(schema: ObjectSchema): (value: Record) => T; +export function requireConfig(...schemas: ObjectSchema[]): (value: Record) => T; +export function requireConfig(...schemas: ObjectSchema[]): (value: Record) => T { + return (value) => { + const results = schemas.map((schema) => { + const result = schema.validate(value, { abortEarly: false, allowUnknown: true }); + if (result.error) { + throw result.error; + } + return result.value; + }); + + return Object.assign({}, ...results); + }; +} diff --git a/packages/backend/src/env.ts b/packages/backend/src/env.ts deleted file mode 100644 index 912e7399..00000000 --- a/packages/backend/src/env.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { accessSync, constants } from 'node:fs'; -import 'dotenv/config'; -import { bool, cleanEnv, makeValidator, port, str } from 'envalid'; - -const file = makeValidator((x) => (accessSync(x, constants.R_OK), x)); - -const env = cleanEnv(process.env, { - NODE_ENV: str({ choices: ['development', 'test', 'production'], default: 'development' }), - PORT: port({ default: 9001 }), - SSL_CERT_FILE: file({ default: undefined }), - SSL_KEY_FILE: file({ default: undefined }), - USE_SSL: bool({ default: false }), -}); - -if (env.USE_SSL) { - if (!env.SSL_CERT_FILE) { - throw new Error('env.CERT_FILE_NAME must be set when using SSL.'); - } - if (!env.SSL_KEY_FILE) { - throw new Error('env.KEY_FILE_NAME must be set when using SSL.'); - } -} - -export default env; diff --git a/packages/backend/src/logger.ts b/packages/backend/src/logger.ts deleted file mode 100644 index 7e51973a..00000000 --- a/packages/backend/src/logger.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { inspect } from 'node:util'; -import chalk, { type Chalk } from 'chalk'; -import jsonStringify_ from 'fast-safe-stringify'; -import { type TransformableInfo } from 'logform'; -import { createLogger, format, transports } from 'winston'; -import 'winston-daily-rotate-file'; - -const logLevels = ['debug', 'verbose', 'info', 'warn', 'error'] as const; - -type LogLevel = typeof logLevels[number]; - -const colors: { [K in LogLevel]: Chalk } = { - error: chalk.bold.redBright, - warn: chalk.bold.yellowBright, - info: chalk.bold.greenBright, - verbose: chalk.bold.cyanBright, - debug: chalk.bold.whiteBright, -}; - -const isPrimitive = (val: any) => val === null || (typeof val !== 'object' && typeof val !== 'function'); - -const formatWithInspect = (val: any): string => { - if (typeof val === 'string') { - return val; - } - const newLine = isPrimitive(val) ? '' : '\n'; - return newLine + inspect(val, { depth: null, colors: true }); -}; - -const jsonStringifyErrors = (_key: string, value: any) => { - if (value instanceof Error) { - const { name, message, stack } = value; - return { name, message, stack }; - } - return value; -}; - -const jsonStringify = (obj: any) => jsonStringify_(obj, jsonStringifyErrors); - -const EXTRA_DATA = Symbol('EXTRA_DATA'); - -interface ExtraData { - label: string; - moreObjects: any[]; -} - -const extractData = (info: TransformableInfo) => ({ - ...(info[EXTRA_DATA as any] as ExtraData), - message: info.message, - level: info.level as LogLevel, - timestamp: info.timestamp as string, -}); - -const log = createLogger({ - transports: [ - new transports.DailyRotateFile({ - level: 'info', - filename: '%DATE%.log', - maxFiles: '14d', - format: format.combine( - format.timestamp(), - format.printf((info) => jsonStringify(extractData(info))), - ), - }), - new transports.Console({ - level: 'debug', - format: format.combine( - format.timestamp({ format: 'MMM DD, HH:mm:ss' }), - format.printf((info) => { - const { label, level, timestamp, message, moreObjects } = extractData(info); - const coloredLabelLevel = colors[level](`${label}:${level}`); - const formattedObjs = moreObjects.map(formatWithInspect).join(' '); - return [timestamp, coloredLabelLevel, message, moreObjects.length ? formattedObjs : ''].join(' '); - }), - ), - }), - ], -}); - -const getLoggerLevel = - (label: string, level: LogLevel) => - (message: string, ...moreObjects: any[]) => { - const extractData: ExtraData = { label, moreObjects }; - log.log(level, message, { label, [EXTRA_DATA]: extractData }); - }; - -export const getLogger = (label: string) => - Object.freeze( - Object.fromEntries(logLevels.map((lvl) => [lvl, getLoggerLevel(label, lvl)])) as { - [K in LogLevel]: ReturnType; - }, - ); diff --git a/packages/backend/src/logger/module.ts b/packages/backend/src/logger/module.ts new file mode 100644 index 00000000..53a636f8 --- /dev/null +++ b/packages/backend/src/logger/module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import { LoggerService } from './service'; + +@Module({ + providers: [LoggerService], + exports: [LoggerService], +}) +export class LoggerModule {} diff --git a/packages/backend/src/logger/service.ts b/packages/backend/src/logger/service.ts new file mode 100644 index 00000000..cab71f25 --- /dev/null +++ b/packages/backend/src/logger/service.ts @@ -0,0 +1,135 @@ +import { inspect } from 'util'; +import { Injectable, LoggerService as NestLoggerService, Optional, Scope } from '@nestjs/common'; +import chalk, { type Chalk } from 'chalk'; +import dayjs, { Dayjs } from 'dayjs'; +import fastSafeStringify from 'fast-safe-stringify'; +import { type TransformableInfo } from 'logform'; +import { createLogger, format, transports } from 'winston'; +import 'winston-daily-rotate-file'; + +export type LogLevel = 'info' | 'error' | 'warn' | 'debug' | 'verbose'; + +interface ExtraData { + moreObjects: any[]; + timestamp: Dayjs; +} + +interface LogEntryData { + context: string; + level: LogLevel; + timestamp: Dayjs; + message: string; + moreObjects: any[]; +} + +@Injectable({ scope: Scope.TRANSIENT }) +export class LoggerService implements NestLoggerService { + private static COLORS: { [K in LogLevel]: Chalk } = { + error: chalk.bold.redBright, + warn: chalk.bold.yellowBright, + info: chalk.bold.greenBright, + verbose: chalk.bold.cyanBright, + debug: chalk.bold.whiteBright, + }; + + private static EXTRA_DATA = Symbol('EXTRA_DATA'); + + private backingLogger = createLogger({ + transports: [ + new transports.DailyRotateFile({ + level: 'info', + filename: '%DATE%.log', + maxFiles: '14d', + format: format.printf((info) => LoggerService.formatForFile(this.extractData(info))), + }), + new transports.Console({ + level: 'debug', + format: format.printf((info) => LoggerService.formatForConsole(this.extractData(info))), + }), + ], + }); + + constructor(@Optional() private context = '') {} + + private static isPrimitive(val: any) { + return val === null || (typeof val !== 'object' && typeof val !== 'function'); + } + + private static formatWithInspect(val: any): string { + if (typeof val === 'string') { + return val; + } + const newLine = LoggerService.isPrimitive(val) ? '' : '\n'; + return newLine + inspect(val, { depth: null, colors: true }); + } + + private static jsonStringifyErrors(_key: string, value: any) { + if (value instanceof Error) { + const { name, message, stack } = value; + return { name, message, stack }; + } + return value; + } + + private static formatForConsole({ context, level, timestamp, message, moreObjects }: LogEntryData): string { + const timeStr = timestamp.format('MMM DD, HH:mm:ss'); + const coloredContextLevel = LoggerService.COLORS[level](`${context}:${level}`); + const formattedObjs = moreObjects.map(LoggerService.formatWithInspect).join(' '); + return [timeStr, coloredContextLevel, message, formattedObjs].join(' '); + } + + private static formatForFile({ timestamp, ...rest }: LogEntryData): string { + return fastSafeStringify({ timestamp: timestamp.format(), ...rest }, LoggerService.jsonStringifyErrors); + } + + private extractData(info: TransformableInfo): LogEntryData { + const data: ExtraData = info[LoggerService.EXTRA_DATA as any]; + return { + context: this.context, + level: info.level as LogLevel, + timestamp: data.timestamp, + message: info.message, + moreObjects: data.moreObjects, + }; + } + + private baseLog(level: LogLevel, message: string, moreObjects: any[]): void { + const extraData: ExtraData = { + moreObjects, + timestamp: dayjs(), + }; + this.backingLogger.log(level, message, { [LoggerService.EXTRA_DATA]: extraData }); + } + + public log(message: string, ...moreObjects: any[]): void { + this.info(message, ...moreObjects); + } + + public info(message: string, ...moreObjects: any[]): void { + this.baseLog('info', message, moreObjects); + } + + public error(message: string, ...moreObjects: any[]): void { + this.baseLog('error', message, moreObjects); + } + + public warn(message: string, ...moreObjects: any[]): void { + this.baseLog('warn', message, moreObjects); + } + + public debug(message: string, ...moreObjects: any[]): void { + this.baseLog('debug', message, moreObjects); + } + + public verbose(message: string, ...moreObjects: any[]): void { + this.baseLog('verbose', message, moreObjects); + } + + public setContext(context: string): void { + this.context = context; + } + + public setLogLevels(): void { + throw new Error('Method not implemented.'); + } +} diff --git a/packages/backend/src/main.ts b/packages/backend/src/main.ts index a02df10e..ceb1cb8f 100644 --- a/packages/backend/src/main.ts +++ b/packages/backend/src/main.ts @@ -1,39 +1,30 @@ -import fs from 'node:fs'; +import fs from 'fs'; +import http from 'http'; +import https from 'https'; +import { NestApplicationOptions } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; +import { ExpressAdapter } from '@nestjs/platform-express'; +import express from 'express'; import Graceful from 'node-graceful'; import { AppModule } from '~/app.module'; +import { requireConfig, expressConfig } from '~/config'; +import { LoggerService } from '~/logger/service'; -import env from '~/env'; -import { getLogger } from '~/logger'; -import { NestApplicationOptions } from '@nestjs/common'; - -const log = getLogger('main'); +const log = new LoggerService('Main'); process.on('uncaughtException', (err) => log.error('uncaughtException', err)); process.on('unhandledRejection', (err) => log.error('unhandledRejection', err)); Graceful.on('exit', (signal, details) => log.info('Exiting.', { signal, details })); (async () => { - const appOpts: NestApplicationOptions = {}; - - // Use standard logger service. - appOpts.logger = { - ...log, - log: log.info, - setLogLevels: () => { - throw new Error('Not implemented.'); - }, + const appOpts: NestApplicationOptions = { + logger: log, }; - // Use SSL if enabled. - if (env.USE_SSL) { - const key = fs.readFileSync(env.SSL_KEY_FILE, 'utf8'); - const cert = fs.readFileSync(env.SSL_CERT_FILE, 'utf8'); - appOpts.httpsOptions = { key, cert }; - } - // Create the app. - const app = await NestFactory.create(AppModule, appOpts); + const server = express(); + const app = await NestFactory.create(AppModule, new ExpressAdapter(server), appOpts); + await app.init(); // Always stop the server when we exit. Graceful.on('exit', async () => { @@ -41,7 +32,19 @@ Graceful.on('exit', (signal, details) => log.info('Exiting.', { signal, details log.info('Closed the app.'); }); - // Listen on specified port. - await app.listen(env.PORT); - log.info('Example app listening on port', env.PORT); + const config = requireConfig(expressConfig)(process.env); + + if (config.PORT != null) { + // Listen on specified HTTP port. + http.createServer(server).listen(config.PORT); + log.info('HTTP listening on port', config.PORT); + } + + if (config.SSL_PORT != null) { + // Listen on specified HTTPS port. + const key = fs.readFileSync(config.SSL_KEY_FILE, 'utf8'); + const cert = fs.readFileSync(config.SSL_CERT_FILE, 'utf8'); + https.createServer({ key, cert }, server).listen(config.SSL_PORT); + log.info('HTTPS listening on port', config.PORT); + } })(); diff --git a/packages/backend/test/app.e2e-spec.ts b/packages/backend/test/app.e2e-spec.ts index 0fd486aa..9b668827 100644 --- a/packages/backend/test/app.e2e-spec.ts +++ b/packages/backend/test/app.e2e-spec.ts @@ -1,7 +1,7 @@ -import * as request from 'supertest'; +import { INestApplication } from '@nestjs/common'; import { Test } from '@nestjs/testing'; +import request from 'supertest'; import { AppModule } from './../src/app.module'; -import { INestApplication } from '@nestjs/common'; describe('AppController (e2e)', () => { let app: INestApplication;