diff --git a/env.example b/env.example new file mode 100644 index 0000000..d14a3e9 --- /dev/null +++ b/env.example @@ -0,0 +1,12 @@ +# APP_NAME=nestapp +# APP_HOST=127.0.0.1 +# APP_PORT=3000 +# APP_SSL=false +# APP_TIMEZONE=Asia/Shanghai +# APP_LOCALE=zh_CN +# APP_FALLBACK_LOCALE=en +# DB_HOST=127.0.0.1 +# DB_PORT=3306 +# DB_USERNAME=3r +# DB_PASSWORD=12345678 +# DB_NAME=3r \ No newline at end of file diff --git a/package.json b/package.json index c012ce3..22b613a 100644 --- a/package.json +++ b/package.json @@ -7,12 +7,12 @@ "license": "UNLICENSED", "scripts": { "prebuild": "rimraf dist", - "build": "nest build", + "build": "cross-env NODE_ENV=production nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", - "start": "nest start", - "start:dev": "nest start --watch", - "start:debug": "nest start --debug --watch", - "start:prod": "node dist/main", + "start": "cross-env NODE_ENV=development nest start", + "start:dev": "cross-env NODE_ENV=development nest start --watch", + "start:debug": "cross-env NODE_ENV=development nest start --debug --watch", + "start:prod": "cross-env NODE_ENV=production node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", @@ -26,9 +26,13 @@ "@nestjs/platform-fastify": "^10.0.3", "@nestjs/swagger": "^7.4.2", "@nestjs/typeorm": "^11.0.0", + "chalk": "^5.4.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.2", "deepmerge": "^4.3.1", + "dotenv": "^16.5.0", + "find-up": "^7.0.0", + "fs-extra": "^11.3.0", "lodash": "^4.17.21", "meilisearch": "^0.50.0", "mysql2": "^3.14.1", @@ -37,7 +41,8 @@ "rxjs": "^7.8.1", "sanitize-html": "^2.17.0", "typeorm": "^0.3.24", - "validator": "^13.15.0" + "validator": "^13.15.0", + "yaml": "^2.8.0" }, "devDependencies": { "@faker-js/faker": "^9.8.0", @@ -46,6 +51,7 @@ "@nestjs/testing": "^10.0.3", "@swc/cli": "^0.1.62", "@swc/core": "^1.3.66", + "@types/fs-extra": "^11.0.4", "@types/jest": "29.5.2", "@types/lodash": "^4.17.16", "@types/node": "^20.3.1", @@ -54,6 +60,7 @@ "@types/validator": "^13.15.1", "@typescript-eslint/eslint-plugin": "^5.60.0", "@typescript-eslint/parser": "^5.60.0", + "cross-env": "^7.0.3", "eslint": "^8.43.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-config-airbnb-typescript": "^17.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 64448e1..4fe46dc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,6 +23,9 @@ importers: '@nestjs/typeorm': specifier: ^11.0.0 version: 11.0.0(@nestjs/common@10.4.17(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.17)(reflect-metadata@0.1.14)(rxjs@7.8.2)(typeorm@0.3.24(better-sqlite3@11.10.0)(mysql2@3.14.1)(reflect-metadata@0.1.14)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.1.6))) + chalk: + specifier: ^5.4.1 + version: 5.4.1 class-transformer: specifier: ^0.5.1 version: 0.5.1 @@ -32,6 +35,15 @@ importers: deepmerge: specifier: ^4.3.1 version: 4.3.1 + dotenv: + specifier: ^16.5.0 + version: 16.5.0 + find-up: + specifier: ^7.0.0 + version: 7.0.0 + fs-extra: + specifier: ^11.3.0 + version: 11.3.0 lodash: specifier: ^4.17.21 version: 4.17.21 @@ -59,6 +71,9 @@ importers: validator: specifier: ^13.15.0 version: 13.15.0 + yaml: + specifier: ^2.8.0 + version: 2.8.0 devDependencies: '@faker-js/faker': specifier: ^9.8.0 @@ -78,6 +93,9 @@ importers: '@swc/core': specifier: ^1.3.66 version: 1.11.24 + '@types/fs-extra': + specifier: ^11.0.4 + version: 11.0.4 '@types/jest': specifier: 29.5.2 version: 29.5.2 @@ -102,6 +120,9 @@ importers: '@typescript-eslint/parser': specifier: ^5.60.0 version: 5.62.0(eslint@8.57.1)(typescript@5.1.6) + cross-env: + specifier: ^7.0.3 + version: 7.0.3 eslint: specifier: ^8.43.0 version: 8.57.1 @@ -822,6 +843,9 @@ packages: '@types/estree@1.0.7': resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} + '@types/fs-extra@11.0.4': + resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==} + '@types/graceful-fs@4.1.9': resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} @@ -846,6 +870,9 @@ packages: '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + '@types/jsonfile@6.1.4': + resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==} + '@types/keyv@3.1.4': resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} @@ -1473,6 +1500,11 @@ packages: create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + cross-env@7.0.3: + resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==} + engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'} + hasBin: true + cross-spawn@5.1.0: resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} @@ -2016,6 +2048,10 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} + find-up@7.0.0: + resolution: {integrity: sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==} + engines: {node: '>=18'} + find-versions@5.1.0: resolution: {integrity: sha512-+iwzCJ7C5v5KgcBuueqVoNiHVoQpwiUK5XFLjf0affFTep+Wcw93tPvmb8tqujDNmzhBDPddnWV/qgWSXgq+Hg==} engines: {node: '>=12'} @@ -2064,6 +2100,10 @@ packages: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} engines: {node: '>=12'} + fs-extra@11.3.0: + resolution: {integrity: sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==} + engines: {node: '>=14.14'} + fs-monkey@1.0.6: resolution: {integrity: sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==} @@ -2688,6 +2728,10 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + locate-path@7.2.0: + resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + lodash.memoize@4.1.2: resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} @@ -3003,6 +3047,10 @@ packages: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} + p-limit@4.0.0: + resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + p-locate@4.1.0: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} @@ -3011,6 +3059,10 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + p-locate@6.0.0: + resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} @@ -3037,6 +3089,10 @@ packages: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} + path-exists@5.0.0: + resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + path-is-absolute@1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} @@ -3907,6 +3963,10 @@ packages: undici-types@6.19.8: resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + unicorn-magic@0.1.0: + resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} + engines: {node: '>=18'} + universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} @@ -4046,6 +4106,11 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yaml@2.8.0: + resolution: {integrity: sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==} + engines: {node: '>= 14.6'} + hasBin: true + yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} @@ -4062,6 +4127,10 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + yocto-queue@1.2.1: + resolution: {integrity: sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==} + engines: {node: '>=12.20'} + snapshots: '@ampproject/remapping@2.3.0': @@ -4911,6 +4980,11 @@ snapshots: '@types/estree@1.0.7': {} + '@types/fs-extra@11.0.4': + dependencies: + '@types/jsonfile': 6.1.4 + '@types/node': 20.17.46 + '@types/graceful-fs@4.1.9': dependencies: '@types/node': 20.17.46 @@ -4936,6 +5010,10 @@ snapshots: '@types/json5@0.0.29': {} + '@types/jsonfile@6.1.4': + dependencies: + '@types/node': 20.17.46 + '@types/keyv@3.1.4': dependencies: '@types/node': 20.17.46 @@ -5684,6 +5762,10 @@ snapshots: create-require@1.1.1: {} + cross-env@7.0.3: + dependencies: + cross-spawn: 7.0.6 + cross-spawn@5.1.0: dependencies: lru-cache: 4.1.5 @@ -6361,6 +6443,12 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 + find-up@7.0.0: + dependencies: + locate-path: 7.2.0 + path-exists: 5.0.0 + unicorn-magic: 0.1.0 + find-versions@5.1.0: dependencies: semver-regex: 4.0.5 @@ -6427,6 +6515,12 @@ snapshots: jsonfile: 6.1.0 universalify: 2.0.1 + fs-extra@11.3.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.1 + fs-monkey@1.0.6: {} fs.realpath@1.0.0: {} @@ -7273,6 +7367,10 @@ snapshots: dependencies: p-locate: 5.0.0 + locate-path@7.2.0: + dependencies: + p-locate: 6.0.0 + lodash.memoize@4.1.2: {} lodash.merge@4.6.2: {} @@ -7571,6 +7669,10 @@ snapshots: dependencies: yocto-queue: 0.1.0 + p-limit@4.0.0: + dependencies: + yocto-queue: 1.2.1 + p-locate@4.1.0: dependencies: p-limit: 2.3.0 @@ -7579,6 +7681,10 @@ snapshots: dependencies: p-limit: 3.1.0 + p-locate@6.0.0: + dependencies: + p-limit: 4.0.0 + p-try@2.2.0: {} package-json-from-dist@1.0.1: {} @@ -7601,6 +7707,8 @@ snapshots: path-exists@4.0.0: {} + path-exists@5.0.0: {} + path-is-absolute@1.0.1: {} path-key@2.0.1: {} @@ -8507,6 +8615,8 @@ snapshots: undici-types@6.19.8: {} + unicorn-magic@0.1.0: {} + universalify@2.0.1: {} unpipe@1.0.0: @@ -8681,6 +8791,8 @@ snapshots: yallist@3.1.1: {} + yaml@2.8.0: {} + yargs-parser@21.1.1: {} yargs@17.7.2: @@ -8696,3 +8808,5 @@ snapshots: yn@3.1.1: {} yocto-queue@0.1.0: {} + + yocto-queue@1.2.1: {} diff --git a/src/modules/config/constants.ts b/src/modules/config/constants.ts new file mode 100644 index 0000000..79acb17 --- /dev/null +++ b/src/modules/config/constants.ts @@ -0,0 +1,8 @@ +export enum EnvironmentType { + DEVELOPMENT = 'development', + DEV = 'dev', + PRODUCTION = 'production', + PROD = 'prod', + TEST = 'test', + PREVIEW = 'preview', +} diff --git a/src/modules/config/env.ts b/src/modules/config/env.ts new file mode 100644 index 0000000..fd6a68c --- /dev/null +++ b/src/modules/config/env.ts @@ -0,0 +1,68 @@ +import dotenv from 'dotenv'; +import { findUpSync } from 'find-up'; +import { readFileSync } from 'fs-extra'; +import { isFunction, isNil } from 'lodash'; + +import { EnvironmentType } from '@/modules/config/constants'; + +export class Env { + async load() { + if (isNil(process.env.NODE_ENV)) { + process.env.NODE_ENV = EnvironmentType.DEVELOPMENT; + } + const envs = [findUpSync(['.env'])]; + if (this.isDev()) { + envs.push( + findUpSync([`.env.${EnvironmentType.DEVELOPMENT}`, `.env.${EnvironmentType.DEV}`]), + ); + } else if (this.isProd) { + envs.push( + findUpSync([`.env.${EnvironmentType.PRODUCTION}`, `.env.${EnvironmentType.PROD}`]), + ); + } else { + envs.push(findUpSync([`.env.${this.run()}`])); + } + + const envFiles = envs.filter((file) => !isNil(file)) as string[]; + const fileEnvs = envFiles + .map((file) => dotenv.parse(readFileSync(file))) + .reduce((o, n) => ({ ...o, ...n }), {}); + const envConfig = { ...process.env, ...fileEnvs }; + const envKeys = Object.keys(envConfig).filter((key) => !(key in process.env)); + envKeys.forEach((key) => { + process.env[key] = envConfig[key]; + }); + } + + run() { + return process.env.NODE_ENV as EnvironmentType & RecordAny; + } + + isProd() { + return this.run() === EnvironmentType.PRODUCTION || this.run() === EnvironmentType.PROD; + } + + isDev() { + return this.run() === EnvironmentType.DEVELOPMENT || this.run() === EnvironmentType.DEV; + } + + get(key?: string, parseTo?: ParseType | T, defaultValue?: T) { + if (!key) { + return process.env; + } + const value = process.env[key]; + if (value !== undefined) { + if (parseTo && isFunction(parseTo)) { + return parseTo(value); + } + return value as T; + } + if (parseTo === undefined && defaultValue === undefined) { + return undefined; + } + if (parseTo && defaultValue === undefined) { + return isFunction(parseTo) ? undefined : parseTo; + } + return defaultValue! as T; + } +}