diff --git a/com.neil-enns.trackaudio.sdPlugin/manifest.json b/com.neil-enns.trackaudio.sdPlugin/manifest.json index e9462e8..6c2dc24 100644 --- a/com.neil-enns.trackaudio.sdPlugin/manifest.json +++ b/com.neil-enns.trackaudio.sdPlugin/manifest.json @@ -1,111 +1,121 @@ { - "Name": "TrackAudio", - "Version": "0.1.0.0", - "Author": "Neil Enns", - "Actions": [ - { - "Name": "ATIS letter", - "UUID": "com.neil-enns.trackaudio.atisletter", - "Icon": "images/actions/atisLetter/cloud-white", - "PropertyInspectorPath": "pi/atisLetter.html", - "Tooltip": "Shows the current ATIS letter for the specified.", - "Controllers": ["Keypad"], - "DisableAutomaticStates": true, - "UserTitleEnabled": false, - "States": [ - { - "Image": "images/actions/default", - "TitleAlignment": "middle" - } - ] - }, - { - "Name": "Hotline", - "UUID": "com.neil-enns.trackaudio.hotline", - "Icon": "images/plugin/phone-solid", - "Tooltip": "Toggles Tx for a hotline frequency.", - "PropertyInspectorPath": "pi/hotline.html", - "DisableAutomaticStates": true, - "UserTitleEnabled": false, - "Controllers": ["Keypad"], - "States": [ - { - "Image": "images/actions/default", - "TitleAlignment": "bottom" - } - ] - }, - { - "Name": "Push to talk", - "UUID": "com.neil-enns.trackaudio.pushtotalk", - "Icon": "images/plugin/microphone-solid", - "Tooltip": "Triggers transmit via push-to-talk.", - "DisableAutomaticStates": true, - "UserTitleEnabled": false, - "PropertyInspectorPath": "pi/pushToTalk.html", - "Controllers": ["Keypad"], - "States": [ - { - "Image": "images/actions/default", - "TitleAlignment": "middle" - } - ] - }, - { - "Name": "Station status", - "UUID": "com.neil-enns.trackaudio.stationstatus", - "Icon": "images/plugin/headphones-solid", - "Tooltip": "Shows the status of a station.", - "DisableAutomaticStates": true, - "Controllers": ["Keypad"], - "PropertyInspectorPath": "pi/stationStatus.html", - "UserTitleEnabled": false, - "States": [ - { - "Image": "images/actions/default", - "TitleAlignment": "middle" - } - ] - }, - { - "Name": "TrackAudio status", - "UUID": "com.neil-enns.trackaudio.trackaudiostatus", - "Icon": "images/plugin/pluginIcon", - "Tooltip": "Shows the status of the connection to TrackAudio.", - "PropertyInspectorPath": "pi/trackAudioStatus.html", - "DisableAutomaticStates": true, - "UserTitleEnabled": false, - "Controllers": ["Keypad"], - "States": [ - { - "Image": "images/actions/default", - "TitleAlignment": "middle" - } - ] - } - ], - "Category": "TrackAudio", - "CategoryIcon": "images/plugin/categoryIcon", - "CodePath": "bin/plugin.js", - "Description": "Provides buttons for controlling TrackAudio", - "Icon": "images/plugin/pluginIcon", - "SDKVersion": 2, - "Software": { - "MinimumVersion": "6.5" - }, - "OS": [ - { - "Platform": "mac", - "MinimumVersion": "10.15" - }, - { - "Platform": "windows", - "MinimumVersion": "10" - } - ], - "Nodejs": { - "Version": "20", - "Debug": "--inspect=127.0.0.1:54545" - }, - "UUID": "com.neil-enns.trackaudio" -} + "Name": "TrackAudio", + "Version": "0.1.0.0", + "Author": "Neil Enns", + "Actions": [ + { + "Name": "ATIS letter", + "UUID": "com.neil-enns.trackaudio.atisletter", + "Icon": "images/actions/atisLetter/cloud-white", + "PropertyInspectorPath": "pi/atisLetter.html", + "Tooltip": "Shows the current ATIS letter for the specified.", + "Controllers": [ + "Keypad" + ], + "DisableAutomaticStates": true, + "UserTitleEnabled": false, + "States": [ + { + "Image": "images/actions/default", + "TitleAlignment": "middle" + } + ] + }, + { + "Name": "Hotline", + "UUID": "com.neil-enns.trackaudio.hotline", + "Icon": "images/plugin/phone-solid", + "Tooltip": "Toggles Tx for a hotline frequency.", + "PropertyInspectorPath": "pi/hotline.html", + "DisableAutomaticStates": true, + "UserTitleEnabled": false, + "Controllers": [ + "Keypad" + ], + "States": [ + { + "Image": "images/actions/default", + "TitleAlignment": "bottom" + } + ] + }, + { + "Name": "Push to talk", + "UUID": "com.neil-enns.trackaudio.pushtotalk", + "Icon": "images/plugin/microphone-solid", + "Tooltip": "Triggers transmit via push-to-talk.", + "DisableAutomaticStates": true, + "UserTitleEnabled": false, + "PropertyInspectorPath": "pi/pushToTalk.html", + "Controllers": [ + "Keypad" + ], + "States": [ + { + "Image": "images/actions/default", + "TitleAlignment": "middle" + } + ] + }, + { + "Name": "Station status", + "UUID": "com.neil-enns.trackaudio.stationstatus", + "Icon": "images/plugin/headphones-solid", + "Tooltip": "Shows the status of a station.", + "DisableAutomaticStates": true, + "Controllers": [ + "Keypad" + ], + "PropertyInspectorPath": "pi/stationStatus.html", + "UserTitleEnabled": false, + "States": [ + { + "Image": "images/actions/default", + "TitleAlignment": "middle" + } + ] + }, + { + "Name": "TrackAudio status", + "UUID": "com.neil-enns.trackaudio.trackaudiostatus", + "Icon": "images/plugin/pluginIcon", + "Tooltip": "Shows the status of the connection to TrackAudio.", + "PropertyInspectorPath": "pi/trackAudioStatus.html", + "DisableAutomaticStates": true, + "UserTitleEnabled": false, + "Controllers": [ + "Keypad" + ], + "States": [ + { + "Image": "images/actions/default", + "TitleAlignment": "middle" + } + ] + } + ], + "Category": "TrackAudio", + "CategoryIcon": "images/plugin/categoryIcon", + "CodePath": "bin/plugin.js", + "Description": "Provides buttons for controlling TrackAudio", + "Icon": "images/plugin/pluginIcon", + "SDKVersion": 2, + "Software": { + "MinimumVersion": "6.5" + }, + "OS": [ + { + "Platform": "mac", + "MinimumVersion": "10.15" + }, + { + "Platform": "windows", + "MinimumVersion": "10" + } + ], + "Nodejs": { + "Version": "20", + "Debug": "--inspect=127.0.0.1:54545" + }, + "UUID": "com.neil-enns.trackaudio" +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index cd393e3..41946af 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,24 +6,28 @@ "": { "dependencies": { "@elgato/streamdeck": "^0.3.0", - "@rollup/plugin-json": "^6.1.0", "axios": "^1.7.2", "chokidar": "^3.6.0", "debounce": "^2.1.0", "handlebars": "^4.7.8", "lru-cache": "^11.0.0", + "streamdeck-transport": "^1.0.1", + "winston": "^3.14.1", "ws": "^8.17.0" }, "devDependencies": { "@elgato/cli": "^0.3.0", "@eslint/js": "^9.2.0", "@rollup/plugin-commonjs": "^26.0.0", + "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^15.2.2", + "@rollup/plugin-replace": "^5.0.7", "@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-typescript": "^11.1.5", "@tsconfig/node20": "^20.1.2", "@types/node": "20.14.14", "@types/ws": "^8.5.10", + "cross-env": "^7.0.3", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "globals": "^15.2.0", @@ -35,6 +39,24 @@ "typescript-eslint": "^8.0.0" } }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, "node_modules/@elgato/cli": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/@elgato/cli/-/cli-0.3.1.tgz", @@ -447,6 +469,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", + "dev": true, "dependencies": { "@rollup/pluginutils": "^5.1.0" }, @@ -487,6 +510,27 @@ } } }, + "node_modules/@rollup/plugin-replace": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.7.tgz", + "integrity": "sha512-PqxSfuorkHz/SPpyngLyg5GCEkOcee9M1bkxiVDr41Pd61mqP1PLOoDPbpl44SB2mQGKwV/In74gqQmGITOhEQ==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "magic-string": "^0.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, "node_modules/@rollup/plugin-terser": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz", @@ -539,6 +583,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", + "dev": true, "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", @@ -789,7 +834,8 @@ "node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true }, "node_modules/@types/node": { "version": "20.14.14", @@ -807,6 +853,11 @@ "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", "dev": true }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==" + }, "node_modules/@types/ws": { "version": "8.5.12", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", @@ -940,8 +991,7 @@ "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/array-union": { "version": "2.1.0", @@ -957,7 +1007,6 @@ "version": "3.2.5", "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", - "dev": true, "license": "MIT" }, "node_modules/asynckit": { @@ -1213,6 +1262,15 @@ "node": ">=0.8" } }, + "node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1228,8 +1286,38 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/color/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "dependencies": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } }, "node_modules/combined-stream": { "version": "1.0.8", @@ -1260,6 +1348,24 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -1400,11 +1506,15 @@ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, "engines": { "node": ">=0.12" }, @@ -1596,7 +1706,8 @@ "node_modules/estree-walker": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true }, "node_modules/esutils": { "version": "2.0.3", @@ -1679,6 +1790,11 @@ "reusify": "^1.0.4" } }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -1797,6 +1913,11 @@ "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" + }, "node_modules/follow-redirects": { "version": "1.15.6", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", @@ -2127,8 +2248,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "4.1.2", @@ -2200,6 +2320,11 @@ "node": ">=8" } }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -2312,6 +2437,17 @@ "@types/estree": "*" } }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-unicode-supported": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", @@ -2423,6 +2559,11 @@ "json-buffer": "3.0.1" } }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -2440,7 +2581,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", - "dev": true, "dependencies": { "uc.micro": "^2.0.0" } @@ -2503,6 +2643,22 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/logform": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.6.1.tgz", + "integrity": "sha512-CdaO738xRapbKIMVn2m4F6KTj4j7ooJ8POVnebSgKo3KBz5axNXRAL7ZdRjIV6NOr2Uf4vjtRkxrFETOioCqSA==", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, "node_modules/lru-cache": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.0.tgz", @@ -2524,7 +2680,6 @@ "version": "14.1.0", "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", - "dev": true, "dependencies": { "argparse": "^2.0.1", "entities": "^4.4.0", @@ -2541,7 +2696,6 @@ "version": "0.34.0", "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.34.0.tgz", "integrity": "sha512-qwGyuyKwjkEMOJ10XN6OTKNOVYvOIi35RNvDLNxTof5s8UmyGHlCdpngRHoRGNvQVGuxO3BJ7uNSgdeX166WXw==", - "dev": true, "dependencies": { "markdown-it": "14.1.0", "markdownlint-micromark": "0.1.9" @@ -2642,7 +2796,6 @@ "version": "0.1.9", "resolved": "https://registry.npmjs.org/markdownlint-micromark/-/markdownlint-micromark-0.1.9.tgz", "integrity": "sha512-5hVs/DzAFa8XqYosbEAEg6ok6MF2smDj89ztn9pKkCtdKHVdPQuGMH7frFfYL9mLkvfFe4pTyAMffLbjf3/EyA==", - "dev": true, "engines": { "node": ">=18" }, @@ -2653,8 +2806,7 @@ "node_modules/mdurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", - "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", - "dev": true + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==" }, "node_modules/merge2": { "version": "1.4.1", @@ -2791,8 +2943,7 @@ "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/mute-stream": { "version": "1.0.0", @@ -2832,6 +2983,14 @@ "wrappy": "1" } }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "dependencies": { + "fn.name": "1.x.x" + } + }, "node_modules/onetime": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", @@ -3091,7 +3250,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", - "dev": true, "engines": { "node": ">=6" } @@ -3136,7 +3294,6 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, "license": "MIT", "dependencies": { "inherits": "^2.0.3", @@ -3264,7 +3421,7 @@ "version": "4.20.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.20.0.tgz", "integrity": "sha512-6rbWBChcnSGzIlXeIdNIZTopKYad8ZG8ajhl78lGRLsI2rX8IkaotQhVas2Ma+GPxJav19wrSzvRvuiv0YKzWw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@types/estree": "1.0.5" @@ -3358,7 +3515,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, "funding": [ { "type": "github", @@ -3374,6 +3530,14 @@ } ] }, + "node_modules/safe-stable-stringify": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", + "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", + "engines": { + "node": ">=10" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -3436,6 +3600,14 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -3481,11 +3653,30 @@ "source-map": "^0.6.0" } }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "engines": { + "node": "*" + } + }, + "node_modules/streamdeck-transport": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/streamdeck-transport/-/streamdeck-transport-1.0.1.tgz", + "integrity": "sha512-rxfX5JvZnk11NMLkHdsyw3YgrrkX3HwUsku+JmZc5ONFC0oTphSosii34FyVBXhelxVh85e+bJVnpGKQiIy6MA==", + "dependencies": { + "@elgato/streamdeck": "^0.3.0", + "logform": "^2.6.1", + "markdownlint": "^0.34.0", + "triple-beam": "^1.4.1", + "winston-transport": "^4.7.1" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" @@ -3653,6 +3844,11 @@ "node": ">=10" } }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -3684,6 +3880,14 @@ "node": ">=8.0" } }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "engines": { + "node": ">= 14.0.0" + } + }, "node_modules/ts-api-utils": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", @@ -3984,8 +4188,7 @@ "node_modules/uc.micro": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", - "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", - "dev": true + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==" }, "node_modules/uglify-js": { "version": "3.19.0", @@ -4018,7 +4221,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, "license": "MIT" }, "node_modules/wcwidth": { @@ -4046,6 +4248,40 @@ "node": ">= 8" } }, + "node_modules/winston": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.14.1.tgz", + "integrity": "sha512-CJi4Il/msz8HkdDfXOMu+r5Au/oyEjFiOZzbX2d23hRLY0narGjqfE5lFlrT5hfYJhPtM8b85/GNFsxIML/RVA==", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.6.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.7.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.7.1.tgz", + "integrity": "sha512-wQCXXVgfv/wUPOfb2x0ruxzwkcZfxcktz6JIMUaPLmcNhO4bZTwA/WtDWK74xV3F2dKu8YadrFv0qhwYjVEwhA==", + "dependencies": { + "logform": "^2.6.1", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", diff --git a/package.json b/package.json index 0f2e52e..7c4d909 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "scripts": { "build": "rollup -c", - "watch": "rollup -c -w --watch.onEnd=\"streamdeck restart com.neil-enns.trackaudio\"", - "pack": "streamdeck pack com.neil-enns.trackaudio.sdPlugin -o .", + "watch": "cross-env NODE_ENV=development rollup -c -w --watch.onEnd=\"streamdeck restart com.neil-enns.trackaudio\"", + "pack": "streamdeck pack com.neil-enns.trackaudio.sdPlugin -f -o .", "link": "streamdeck link com.neil-enns.trackaudio.sdPlugin", "lint": "npm run eslint && npm run markdownlint", "eslint": "eslint src --report-unused-disable-directives --max-warnings 0", @@ -13,12 +13,15 @@ "@elgato/cli": "^0.3.0", "@eslint/js": "^9.2.0", "@rollup/plugin-commonjs": "^26.0.0", + "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^15.2.2", + "@rollup/plugin-replace": "^5.0.7", "@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-typescript": "^11.1.5", "@tsconfig/node20": "^20.1.2", "@types/node": "20.14.14", "@types/ws": "^8.5.10", + "cross-env": "^7.0.3", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "globals": "^15.2.0", @@ -31,12 +34,13 @@ }, "dependencies": { "@elgato/streamdeck": "^0.3.0", - "@rollup/plugin-json": "^6.1.0", "axios": "^1.7.2", "chokidar": "^3.6.0", "debounce": "^2.1.0", "handlebars": "^4.7.8", "lru-cache": "^11.0.0", + "streamdeck-transport": "^1.0.1", + "winston": "^3.14.1", "ws": "^8.17.0" } } diff --git a/rollup.config.mjs b/rollup.config.mjs index 38f1a03..7f2f314 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -5,6 +5,7 @@ import typescript from "@rollup/plugin-typescript"; import path from "node:path"; import url from "node:url"; import json from "@rollup/plugin-json"; +import replace from "@rollup/plugin-replace"; const isWatching = !!process.env.ROLLUP_WATCH; const sdPlugin = "com.neil-enns.trackaudio.sdPlugin"; @@ -24,6 +25,17 @@ const config = { }, }, plugins: [ + replace({ + preventAssignment: true, + values: { + "process.env.NODE_ENV": JSON.stringify( + process.env.NODE_ENV ?? "production" + ), + "process.env.LOG_LEVEL": process.env.LOG_LEVEL + ? JSON.stringify(process.env.NODE_ENV) + : undefined, + }, + }), { name: "watch-externals", buildStart: function () { diff --git a/src/managers/action.ts b/src/managers/action.ts index e5a8d38..9de90ad 100644 --- a/src/managers/action.ts +++ b/src/managers/action.ts @@ -31,6 +31,9 @@ import debounce from "debounce"; import { EventEmitter } from "events"; import vatsimManager from "./vatsim"; import { PushToTalkSettings } from "@actions/pushToTalk"; +import mainLogger from "@utils/logger"; + +const logger = mainLogger.child({ service: "action" }); /** * Singleton class that manages StreamDeck actions @@ -707,7 +710,7 @@ class ActionManager extends EventEmitter { public showAlertOnAll() { this.actions.forEach((entry) => { entry.action.showAlert().catch((error: unknown) => { - console.error(error); + logger.error(error); }); }); } diff --git a/src/managers/svg.ts b/src/managers/svg.ts index 0270c85..d031d74 100644 --- a/src/managers/svg.ts +++ b/src/managers/svg.ts @@ -3,6 +3,9 @@ import Handlebars from "handlebars"; import path from "path"; import * as chokidar from "chokidar"; import EventEmitter from "events"; +import mainLogger from "@utils/logger"; + +const logger = mainLogger.child({ service: "svg" }); export type CompiledSvgTemplate = | ReturnType @@ -99,7 +102,7 @@ class SvgTemplateManager extends EventEmitter { return compiledTemplate; } catch (err: unknown) { - console.error(err); + logger.error(err); } return undefined; diff --git a/src/managers/trackAudio.ts b/src/managers/trackAudio.ts index e49d441..bad8b4a 100644 --- a/src/managers/trackAudio.ts +++ b/src/managers/trackAudio.ts @@ -13,6 +13,9 @@ import { } from "@interfaces/messages"; import { EventEmitter } from "events"; import WebSocket from "ws"; +import mainLogger from "@utils/logger"; + +const logger = mainLogger.child({ service: "trackAudio" }); /** * Manages the websocket connection to TrackAudio. @@ -73,7 +76,7 @@ class TrackAudioManager extends EventEmitter { */ public connect(): void { if (this.socket && this.socket.readyState !== WebSocket.CLOSED) { - console.warn("WebSocket is already connected or connecting."); + logger.warn("WebSocket is already connected or connecting."); return; } @@ -86,12 +89,12 @@ class TrackAudioManager extends EventEmitter { this.socket = new WebSocket(this.url); this.socket.on("open", () => { - console.debug("WebSocket connection established."); + logger.debug("WebSocket connection established."); this.emit("connected"); }); this.socket.on("close", () => { - console.debug("WebSocket connection closed"); + logger.debug("WebSocket connection closed"); this._isVoiceConnected = false; this.emit("disconnected"); @@ -100,11 +103,11 @@ class TrackAudioManager extends EventEmitter { this.socket.on("error", (err: Error & { code: string }) => { if (err.code === "ECONNREFUSED") { - console.error( + logger.debug( "Unable to connect to TrackAudio, connection refused. TrackAudio probably isn't running." ); } else { - console.error("WebSocket error:", err.message); + logger.error("WebSocket error:", err.message); } this._isVoiceConnected = false; @@ -122,7 +125,7 @@ class TrackAudioManager extends EventEmitter { * @param message The message to process */ private processMessage(message: string): void { - console.debug("Received: %s", message); + logger.debug("Received: %s", message); const data = JSON.parse(message) as IncomingMessage; @@ -193,7 +196,7 @@ class TrackAudioManager extends EventEmitter { } this.reconnectTimer = setTimeout(() => { - console.debug(`Attempting to reconnect...`); + logger.debug(`Attempting to reconnect...`); this.connect(); }, this.reconnectInterval); } diff --git a/src/managers/vatsim.ts b/src/managers/vatsim.ts index 780bb9d..6822d8e 100644 --- a/src/managers/vatsim.ts +++ b/src/managers/vatsim.ts @@ -2,6 +2,9 @@ import { VatsimData } from "@interfaces/vatsim"; import { handleAsyncException } from "@root/utils/handleAsyncException"; import axios from "axios"; import EventEmitter from "events"; +import mainLogger from "@utils/logger"; + +const logger = mainLogger.child({ service: "vatsim" }); /** * Singleton class that manages communication with VATSIM. @@ -34,7 +37,7 @@ class VatsimManager extends EventEmitter { this.emit("vatsimDataReceived", data); } catch (error) { - console.error("Error fetching VATSIM data: ", error); + logger.error("Error fetching VATSIM data: ", error); } } diff --git a/src/plugin.ts b/src/plugin.ts index b2ac2fa..14fb02b 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -31,13 +31,16 @@ import { handleTxEnd } from "@eventHandlers/trackAudio/txEnd"; import { handleVoiceConnectedState } from "@eventHandlers/trackAudio/voiceConnectedState"; import { handleVatsimDataReceived } from "@eventHandlers/vatsim/vatsimDataReceived"; import { handleActionAdded } from "@eventHandlers/action/actionAdded"; +import mainLogger from "@utils/logger"; + +const logger = mainLogger.child({ service: "plugin" }); // Flag to prevent handling repeated disconnect events let disconnectHandled = false; // Register for uncaught exceptions process.on("uncaughtException", (error) => { - console.error("Uncaught Exception:", error); + logger.error("Uncaught Exception:", error); }); // Register all the event handlers diff --git a/src/utils/handleAsyncException.ts b/src/utils/handleAsyncException.ts index 2a19046..feec6ba 100644 --- a/src/utils/handleAsyncException.ts +++ b/src/utils/handleAsyncException.ts @@ -1,4 +1,8 @@ +import mainLogger from "@utils/logger"; + +const logger = mainLogger.child({ service: "system" }); + export const handleAsyncException = (preamble: string, error: unknown) => { const err = error as Error; - console.error(`${preamble}${err.message}`); + logger.error(`${preamble}${err.message}`); }; diff --git a/src/utils/logger.ts b/src/utils/logger.ts new file mode 100644 index 0000000..65dd057 --- /dev/null +++ b/src/utils/logger.ts @@ -0,0 +1,67 @@ +import winston from "winston"; +import StreamdeckTransport from "streamdeck-transport"; + +/** + * Additional properties for Winston info objects to keep TypeScript happy. + */ +interface TrackAudioLogInfo extends winston.Logform.TransformableInfo { + service: string; + message: string; +} + +/** + * Singleton class to manage a Winston logger across the entire plugin. + */ +class Logger { + private static _instance: Logger | null = null; + public winston: ReturnType; + + /** + * Creates a new instance of the Winston logger with Console and StreamdeckTransport + * transports, configured with a log level set via either NODE_ENV or LOG_LEVEL + * environment variables at build time. + */ + private constructor() { + this.winston = winston.createLogger({ + level: this.level(), + transports: [ + new winston.transports.Console({ forceConsole: true }), + new StreamdeckTransport({ + scope: "trackaudio", + format: winston.format.printf((info) => { + const customInfo = info as TrackAudioLogInfo; + return `[${customInfo.service}] ${customInfo.message}`; + }), + }), + ], + }); + } + + /** + * Determines the log level based on either the LOG_LEVEL or NODE_ENV environment + * variables. These environment variables are set at *build* time, not at runtime, + * using the @rollup/plugin-replace step in rollup.config.mjs. + * @returns The log level. + */ + private level() { + if (process.env.LOG_LEVEL !== undefined) { + return process.env.LOG_LEVEL; + } + + return process.env.NODE_ENV === "development" ? "debug" : "warn"; + } + + /** + * Provides access to the Logger instance. + * @returns The instance of Logger + */ + public static getInstance(): Logger { + if (!Logger._instance) { + Logger._instance = new Logger(); + } + return Logger._instance; + } +} + +const loggerInstance = Logger.getInstance(); +export default loggerInstance.winston; diff --git a/tsconfig.json b/tsconfig.json index fb5d140..317e905 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,7 +11,8 @@ "@helpers/*": ["./src/helpers/*"], "@interfaces/*": ["./src/interfaces/*"], "@managers/*": ["./src/managers/*"], - "@root/*": ["./src/*"] + "@utils/*": ["./src/utils/*"], + "@root/*": ["./src/*"], } }, "include": ["src/**/*.ts"],