diff --git a/.gitignore b/.gitignore index c4db8f8..b10540c 100644 --- a/.gitignore +++ b/.gitignore @@ -39,4 +39,8 @@ testem.log .DS_Store Thumbs.db -.nx/cache \ No newline at end of file +.nx/cache + +# Local Config +.env +local.config.json diff --git a/README.md b/README.md index 59ec41e..9d1d3d4 100644 --- a/README.md +++ b/README.md @@ -16,13 +16,14 @@ imports: [..., RuntimeConfigLoaderModule, ...], That's it; it's that simple. In the `RuntimeConfigLoaderModule`, the `APP_INITIALIZER` token is used to run a function which loads the configuration from a file or an API endpoint that can be used throughout the application. -If you implement the library exactly as it is above, the configuration file needs to be in the `./assets/config.json` location as mentioned above. If you'd like to load the file from a different location, provide that location in the `.forRoot()` method when importing the `RuntimeConfigLoaderModule`: +If you implement the library exactly as it is above, the configuration file needs to be in the `./assets/config.json` location as mentioned above. If you'd like to load the file from a different location, provide that using an injection token in the app's main providers array: ```ts -imports: [ +providers: [ ..., - RuntimeConfigLoaderModule.forRoot( - { configUrl: './path/to/config/config.json' } + { + provide: RUNTIME_CONFIG_LOADER_CONFIG, + useValue: { configUrl: './path/to/config/config.json' } ), ...] ``` @@ -30,29 +31,35 @@ imports: [ If you want to load multiple files, the value of `configUrl` should be an array of strings: ```ts -imports: [ +providers: [ ..., - RuntimeConfigLoaderModule.forRoot( - { configUrl: ['./path/to/config/config-1.json', './path/to/config/config-2.json'] } + { + provide: RUNTIME_CONFIG_LOADER_CONFIG, + useValue: { configUrl: ['./path/to/config/config-1.json', './path/to/config/config-2.json'] } ), ...] ``` > Make sure that the path(s) you provide here is accessible by the Angular application, meaning that the file is somewhere the app can load it. In my opinion, the `assets` folder is the easiest place to work from. -## Multiple Config Paths +## Local Config File -One reason you may want to load multiple configuration objects is so that you can set the configuration on your machine without affecting anyone else. For example, you could have a `local.config.json` file that is not included in source control. Some of the values in that file would overwrite the values in a config file that everyone can use. Another use case is that some config values don't change between environments, and some do. The ones that don't change could go in one file, the ones that do change could go in a second file. Each developer/environment can provide the second file with values they want or need. +One reason you may want to load multiple configuration objects is so that you can set the configuration on your machine without affecting anyone else. For example, you could have a `local.config.json` file that is not included in source control. Some of the values in that file would overwrite the values in a config file that everyone can use. There is an improved way of providing the local config file for your app. There are two new attributes on the config object, `localConfigUrl` and `useLocalConfig`. The first, `localConfigUrl`, is the path to the local config file. Again, generally, this file generally won't be checked in to source control. The second is called `userLocalConfig`. The recommendation is to set the value of this to `isDevMode()` from `@angular/core`, but you could set the value of this to true or false based on any other condition you would like. -It's important to know that if an attribute is repeated in two configuration files, the latest value is kept. So, let's say you have `apiUrl` in both files, `config-1.json` and `config-2.json`. Let's assume the files are passed in to the `forRoot` method like this: +The values in the local config file will overwrite all previous values on other config objects that have been loaded. This way you can have a general app config file that everyone uses, but you can overwrite it on your machine without worrying about checking it in and messing up other people's applications. + +> Note: If you provide a file URL and tell the service to load that config, it will load it. If the file doesn't exist, it will produce a 404. That's why it's recommended that the `useLocalConfig` value is set to `isDevMode()`, so that you don't have a 404 show up in production when it shouldn't. ```ts imports: [ ..., - RuntimeConfigLoaderModule.forRoot( - { configUrl: ['./path/to/config/config-1.json', './path/to/config/config-2.json'] } + { + provide: RUNTIME_CONFIG_LOADER_CONFIG, + useValue: { + configUrl: './path/to/config/config.json', + localConfigUrl: './path/to/config/local.config.json', + useLocalConfig: isDevMode() + } ), ...] ``` - -In this case, the `apiUrl` value from `config-2.json` will override the value from `config-1.json`. diff --git a/apps/runtime-config-loader-demo/src/app/app.module.ts b/apps/runtime-config-loader-demo/src/app/app.module.ts index 3c0df45..ad44d1d 100644 --- a/apps/runtime-config-loader-demo/src/app/app.module.ts +++ b/apps/runtime-config-loader-demo/src/app/app.module.ts @@ -1,18 +1,25 @@ -import { NgModule } from '@angular/core'; +import { NgModule, isDevMode } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; -import { RuntimeConfigLoaderModule } from 'runtime-config-loader'; +import { + RUNTIME_CONFIG_LOADER_CONFIG, + RuntimeConfigLoaderModule, +} from 'runtime-config-loader'; import { AppComponent } from './app.component'; @NgModule({ declarations: [AppComponent], - imports: [ - BrowserModule, - RuntimeConfigLoaderModule.forRoot({ - configUrl: './assets/config/config.json', - }), + imports: [BrowserModule, RuntimeConfigLoaderModule], + providers: [ + { + provide: RUNTIME_CONFIG_LOADER_CONFIG, + useValue: { + configUrl: './assets/config/config.json', + localConfigUrl: './assets/config/local.config.json', + useLocalConfig: isDevMode(), + }, + }, ], - providers: [], bootstrap: [AppComponent], }) export class AppModule {} diff --git a/apps/runtime-config-loader-demo/src/assets/config/config.json b/apps/runtime-config-loader-demo/src/assets/config/config.json index 919fe08..9aca9a9 100644 --- a/apps/runtime-config-loader-demo/src/assets/config/config.json +++ b/apps/runtime-config-loader-demo/src/assets/config/config.json @@ -1,3 +1,8 @@ { - "testValue": "This is a test" + "unique": "This is a unique value", + "testValue": "This is a test", + "deepMergeExample": { + "attr1": "value 1", + "attr2": "value 2" + } } diff --git a/libs/runtime-config-loader/README.md b/libs/runtime-config-loader/README.md index 59ec41e..9d1d3d4 100644 --- a/libs/runtime-config-loader/README.md +++ b/libs/runtime-config-loader/README.md @@ -16,13 +16,14 @@ imports: [..., RuntimeConfigLoaderModule, ...], That's it; it's that simple. In the `RuntimeConfigLoaderModule`, the `APP_INITIALIZER` token is used to run a function which loads the configuration from a file or an API endpoint that can be used throughout the application. -If you implement the library exactly as it is above, the configuration file needs to be in the `./assets/config.json` location as mentioned above. If you'd like to load the file from a different location, provide that location in the `.forRoot()` method when importing the `RuntimeConfigLoaderModule`: +If you implement the library exactly as it is above, the configuration file needs to be in the `./assets/config.json` location as mentioned above. If you'd like to load the file from a different location, provide that using an injection token in the app's main providers array: ```ts -imports: [ +providers: [ ..., - RuntimeConfigLoaderModule.forRoot( - { configUrl: './path/to/config/config.json' } + { + provide: RUNTIME_CONFIG_LOADER_CONFIG, + useValue: { configUrl: './path/to/config/config.json' } ), ...] ``` @@ -30,29 +31,35 @@ imports: [ If you want to load multiple files, the value of `configUrl` should be an array of strings: ```ts -imports: [ +providers: [ ..., - RuntimeConfigLoaderModule.forRoot( - { configUrl: ['./path/to/config/config-1.json', './path/to/config/config-2.json'] } + { + provide: RUNTIME_CONFIG_LOADER_CONFIG, + useValue: { configUrl: ['./path/to/config/config-1.json', './path/to/config/config-2.json'] } ), ...] ``` > Make sure that the path(s) you provide here is accessible by the Angular application, meaning that the file is somewhere the app can load it. In my opinion, the `assets` folder is the easiest place to work from. -## Multiple Config Paths +## Local Config File -One reason you may want to load multiple configuration objects is so that you can set the configuration on your machine without affecting anyone else. For example, you could have a `local.config.json` file that is not included in source control. Some of the values in that file would overwrite the values in a config file that everyone can use. Another use case is that some config values don't change between environments, and some do. The ones that don't change could go in one file, the ones that do change could go in a second file. Each developer/environment can provide the second file with values they want or need. +One reason you may want to load multiple configuration objects is so that you can set the configuration on your machine without affecting anyone else. For example, you could have a `local.config.json` file that is not included in source control. Some of the values in that file would overwrite the values in a config file that everyone can use. There is an improved way of providing the local config file for your app. There are two new attributes on the config object, `localConfigUrl` and `useLocalConfig`. The first, `localConfigUrl`, is the path to the local config file. Again, generally, this file generally won't be checked in to source control. The second is called `userLocalConfig`. The recommendation is to set the value of this to `isDevMode()` from `@angular/core`, but you could set the value of this to true or false based on any other condition you would like. -It's important to know that if an attribute is repeated in two configuration files, the latest value is kept. So, let's say you have `apiUrl` in both files, `config-1.json` and `config-2.json`. Let's assume the files are passed in to the `forRoot` method like this: +The values in the local config file will overwrite all previous values on other config objects that have been loaded. This way you can have a general app config file that everyone uses, but you can overwrite it on your machine without worrying about checking it in and messing up other people's applications. + +> Note: If you provide a file URL and tell the service to load that config, it will load it. If the file doesn't exist, it will produce a 404. That's why it's recommended that the `useLocalConfig` value is set to `isDevMode()`, so that you don't have a 404 show up in production when it shouldn't. ```ts imports: [ ..., - RuntimeConfigLoaderModule.forRoot( - { configUrl: ['./path/to/config/config-1.json', './path/to/config/config-2.json'] } + { + provide: RUNTIME_CONFIG_LOADER_CONFIG, + useValue: { + configUrl: './path/to/config/config.json', + localConfigUrl: './path/to/config/local.config.json', + useLocalConfig: isDevMode() + } ), ...] ``` - -In this case, the `apiUrl` value from `config-2.json` will override the value from `config-1.json`. diff --git a/libs/runtime-config-loader/package.json b/libs/runtime-config-loader/package.json index 0c0b12d..ab3602f 100644 --- a/libs/runtime-config-loader/package.json +++ b/libs/runtime-config-loader/package.json @@ -1,6 +1,6 @@ { "name": "runtime-config-loader", - "version": "5.0.2", + "version": "6.0.0", "author": { "email": "preston.j.lamb@gmail.com", "name": "Preston Lamb", @@ -23,9 +23,9 @@ "config" ], "peerDependencies": { - "@angular/common": ">=13.0.0", - "@angular/core": ">=13.0.0", - "@angular/platform-browser-dynamic": ">=13.0.0", + "@angular/common": ">=16.0.0", + "@angular/core": ">=16.0.0", + "@angular/platform-browser-dynamic": ">=16.0.0", "rxjs": ">=6.6.0" }, "dependencies": { diff --git a/libs/runtime-config-loader/src/lib/runtime-config-loader.module.ts b/libs/runtime-config-loader/src/lib/runtime-config-loader.module.ts index 2eab2b4..5b751cd 100644 --- a/libs/runtime-config-loader/src/lib/runtime-config-loader.module.ts +++ b/libs/runtime-config-loader/src/lib/runtime-config-loader.module.ts @@ -1,6 +1,5 @@ import { HttpClientModule } from '@angular/common/http'; -import { APP_INITIALIZER, ModuleWithProviders, NgModule } from '@angular/core'; -import { RuntimeConfig } from './runtime-config'; +import { APP_INITIALIZER, NgModule } from '@angular/core'; import { RuntimeConfigLoaderService } from './runtime-config-loader/runtime-config-loader.service'; export function initConfig(configSvc: RuntimeConfigLoaderService) { @@ -19,19 +18,4 @@ export function initConfig(configSvc: RuntimeConfigLoaderService) { }, ], }) -export class RuntimeConfigLoaderModule { - static forRoot( - config: RuntimeConfig - ): ModuleWithProviders { - return { - ngModule: RuntimeConfigLoaderModule, - providers: [ - { - provide: RuntimeConfig, - useValue: config, - }, - RuntimeConfigLoaderService, - ], - }; - } -} +export class RuntimeConfigLoaderModule {} diff --git a/libs/runtime-config-loader/src/lib/runtime-config-loader/runtime-config-loader.service.spec.ts b/libs/runtime-config-loader/src/lib/runtime-config-loader/runtime-config-loader.service.spec.ts index 09698fc..65d9399 100644 --- a/libs/runtime-config-loader/src/lib/runtime-config-loader/runtime-config-loader.service.spec.ts +++ b/libs/runtime-config-loader/src/lib/runtime-config-loader/runtime-config-loader.service.spec.ts @@ -40,44 +40,35 @@ describe('RuntimeConfigLoaderService', () => { expect(service).toBeTruthy(); }); - it( - 'should load the config object', - waitForAsync(() => { - mockHttpService.get = jest - .fn() - .mockReturnValue(of(mockConfigData1)); - service.loadConfig().subscribe(); - - const config = service.getConfig(); - expect(config).toStrictEqual(mockConfigData1); - }) - ); - - it( - 'should return the key from the config object', - waitForAsync(() => { - mockHttpService.get = jest - .fn() - .mockReturnValue(of(mockConfigData1)); - service.loadConfig().subscribe(); - - const configKey = service.getConfigObjectKey('apiUrl'); - expect(configKey).toStrictEqual(mockConfigData1.apiUrl); - }) - ); - - it( - 'should handle a load error', - waitForAsync(() => { - mockHttpService.get = jest - .fn() - .mockReturnValue(throwError('Test Error')); - service.loadConfig().subscribe(); - - const config = service.getConfig(); - expect(config).toStrictEqual(null); - }) - ); + it('should load the config object', waitForAsync(() => { + mockHttpService.get = jest + .fn() + .mockReturnValue(of(mockConfigData1)); + service.loadConfig().subscribe(); + + const config = service.getConfig(); + expect(config).toStrictEqual(mockConfigData1); + })); + + it('should return the key from the config object', waitForAsync(() => { + mockHttpService.get = jest + .fn() + .mockReturnValue(of(mockConfigData1)); + service.loadConfig().subscribe(); + + const configKey = service.getConfigObjectKey('apiUrl'); + expect(configKey).toStrictEqual(mockConfigData1.apiUrl); + })); + + it('should handle a load error', waitForAsync(() => { + mockHttpService.get = jest + .fn() + .mockReturnValue(throwError('Test Error')); + service.loadConfig().subscribe(); + + const config = service.getConfig(); + expect(config).toStrictEqual(null); + })); }); describe('Double Config URL', () => { @@ -93,60 +84,48 @@ describe('RuntimeConfigLoaderService', () => { expect(service).toBeTruthy(); }); - it( - 'should load the config object from both sources', - waitForAsync(() => { - mockHttpService.get = jest - .fn() - .mockReturnValueOnce(of(mockConfigData1)) - .mockReturnValueOnce(of(mockConfigData2)); - service.loadConfig().subscribe(); - - const config = service.getConfig(); - expect(config).toStrictEqual(mockConfigData2); - }) - ); - - it( - 'should return the key from the config object after combining both sources', - waitForAsync(() => { - mockHttpService.get = jest - .fn() - .mockReturnValueOnce(of(mockConfigData1)) - .mockReturnValueOnce(of(mockConfigData2)); - service.loadConfig().subscribe(); - - const configKey = service.getConfigObjectKey('apiUrl'); - expect(configKey).toStrictEqual(mockConfigData2.apiUrl); - }) - ); - - it( - 'should handle a load error when the first file fails', - waitForAsync(() => { - mockHttpService.get = jest - .fn() - .mockReturnValueOnce(throwError('Test Error')) - .mockReturnValueOnce(of(mockConfigData2)); - service.loadConfig().subscribe(); - - const config = service.getConfig(); - expect(config).toStrictEqual(null); - }) - ); - - it( - 'should handle a load error when the second file fails', - waitForAsync(() => { - mockHttpService.get = jest - .fn() - .mockReturnValueOnce(of(mockConfigData1)) - .mockReturnValueOnce(throwError('Test Error')); - service.loadConfig().subscribe(); - - const config = service.getConfig(); - expect(config).toStrictEqual(null); - }) - ); + it('should load the config object from both sources', waitForAsync(() => { + mockHttpService.get = jest + .fn() + .mockReturnValueOnce(of(mockConfigData1)) + .mockReturnValueOnce(of(mockConfigData2)); + service.loadConfig().subscribe(); + + const config = service.getConfig(); + expect(config).toStrictEqual(mockConfigData2); + })); + + it('should return the key from the config object after combining both sources', waitForAsync(() => { + mockHttpService.get = jest + .fn() + .mockReturnValueOnce(of(mockConfigData1)) + .mockReturnValueOnce(of(mockConfigData2)); + service.loadConfig().subscribe(); + + const configKey = service.getConfigObjectKey('apiUrl'); + expect(configKey).toStrictEqual(mockConfigData2.apiUrl); + })); + + it('should handle a load error when the first file fails', waitForAsync(() => { + mockHttpService.get = jest + .fn() + .mockReturnValueOnce(throwError('Test Error')) + .mockReturnValueOnce(of(mockConfigData2)); + service.loadConfig().subscribe(); + + const config = service.getConfig(); + expect(config).toStrictEqual(null); + })); + + it('should handle a load error when the second file fails', waitForAsync(() => { + mockHttpService.get = jest + .fn() + .mockReturnValueOnce(of(mockConfigData1)) + .mockReturnValueOnce(throwError('Test Error')); + service.loadConfig().subscribe(); + + const config = service.getConfig(); + expect(config).toStrictEqual(null); + })); }); }); diff --git a/libs/runtime-config-loader/src/lib/runtime-config-loader/runtime-config-loader.service.ts b/libs/runtime-config-loader/src/lib/runtime-config-loader/runtime-config-loader.service.ts index 1362d81..7fa4609 100644 --- a/libs/runtime-config-loader/src/lib/runtime-config-loader/runtime-config-loader.service.ts +++ b/libs/runtime-config-loader/src/lib/runtime-config-loader/runtime-config-loader.service.ts @@ -1,20 +1,23 @@ import { HttpClient } from '@angular/common/http'; -import { Injectable, Optional } from '@angular/core'; -import { RuntimeConfig } from '../runtime-config'; -import { forkJoin, Observable, of, Subject, zip } from 'rxjs'; +import { Inject, Injectable, inject } from '@angular/core'; +import { merge } from 'lodash'; +import { Observable, Subject, forkJoin, of } from 'rxjs'; import { catchError, take, tap } from 'rxjs/operators'; +import { RUNTIME_CONFIG_LOADER_CONFIG, RuntimeConfig } from '../runtime-config'; @Injectable() export class RuntimeConfigLoaderService { - private configUrl: string | string[] = './assets/config.json'; + private configUrl: string | string[] = + (this.config && this.config.configUrl) || './assets/config.json'; + private localConfigUrl: string = + (this.config && this.config.localConfigUrl) || ''; private configObject: any = null; public configSubject: Subject = new Subject(); - constructor(private _http: HttpClient, @Optional() config: RuntimeConfig) { - if (config) { - this.configUrl = config.configUrl; - } - } + constructor( + private _http: HttpClient, + @Inject(RUNTIME_CONFIG_LOADER_CONFIG) private config: RuntimeConfig + ) {} loadConfig(): Observable { const urls: string[] = Array.isArray(this.configUrl) @@ -25,11 +28,21 @@ export class RuntimeConfigLoaderService { this.makeHttpCall(url) ); + if (this.localConfigUrl && this.config.useLocalConfig) { + const localConfig = this.makeHttpCall(this.localConfigUrl).pipe( + catchError(() => { + return of({}); + }) + ); + + observables.push(localConfig); + } + return forkJoin(observables).pipe( tap((configDataArray: any[]) => { this.configObject = configDataArray.reduce( (acc, configData) => { - return { ...acc, ...configData }; + return merge(acc, configData); }, {} ); diff --git a/libs/runtime-config-loader/src/lib/runtime-config.ts b/libs/runtime-config-loader/src/lib/runtime-config.ts index d39f91b..a3f22e9 100644 --- a/libs/runtime-config-loader/src/lib/runtime-config.ts +++ b/libs/runtime-config-loader/src/lib/runtime-config.ts @@ -1,7 +1,25 @@ +import { InjectionToken, isDevMode } from '@angular/core'; + export class RuntimeConfig { configUrl: string | string[]; + localConfigUrl?: string; + useLocalConfig? = isDevMode(); constructor(obj: any = {}) { this.configUrl = obj.configUrl || './assets/config.json'; + this.localConfigUrl = obj.localConfigUrl || ''; + this.useLocalConfig = obj.useLocalConfig || isDevMode(); } } + +export const RUNTIME_CONFIG_LOADER_CONFIG = new InjectionToken( + 'RUNTIME_CONFIG_LOADER_CONFIG', + { + providedIn: 'root', + factory: () => ({ + configUrl: './assets/config.json', + localConfigUrl: '', + useLocalConfig: isDevMode(), + }), + } +); diff --git a/package-lock.json b/package-lock.json index bb91413..905514c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "@angular/platform-browser-dynamic": "17.0.2", "@angular/router": "17.0.2", "@nx/angular": "17.1.2", + "lodash": "^4.17.21", "rxjs": "~6.6.0", "tslib": "^2.0.0", "zone.js": "0.14.2" @@ -40,6 +41,7 @@ "@nx/workspace": "17.1.2", "@schematics/angular": "17.0.0", "@types/jest": "29.4.4", + "@types/lodash": "^4.14.201", "@types/node": "14.14.33", "@typescript-eslint/eslint-plugin": "6.11.0", "@typescript-eslint/parser": "6.11.0", @@ -6388,6 +6390,12 @@ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" }, + "node_modules/@types/lodash": { + "version": "4.14.201", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.201.tgz", + "integrity": "sha512-y9euML0cim1JrykNxADLfaG0FgD1g/yTHwUs/Jg9ZIU7WKj2/4IW9Lbb1WZbvck78W/lfGXFfe+u2EGfIJXdLQ==", + "dev": true + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -27015,6 +27023,12 @@ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" }, + "@types/lodash": { + "version": "4.14.201", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.201.tgz", + "integrity": "sha512-y9euML0cim1JrykNxADLfaG0FgD1g/yTHwUs/Jg9ZIU7WKj2/4IW9Lbb1WZbvck78W/lfGXFfe+u2EGfIJXdLQ==", + "dev": true + }, "@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", diff --git a/package.json b/package.json index c9621f8..8be3f81 100644 --- a/package.json +++ b/package.json @@ -22,10 +22,11 @@ "@angular/platform-browser": "17.0.2", "@angular/platform-browser-dynamic": "17.0.2", "@angular/router": "17.0.2", + "@nx/angular": "17.1.2", + "lodash": "^4.17.21", "rxjs": "~6.6.0", "tslib": "^2.0.0", - "zone.js": "0.14.2", - "@nx/angular": "17.1.2" + "zone.js": "0.14.2" }, "devDependencies": { "@angular-devkit/build-angular": "17.0.0", @@ -38,11 +39,13 @@ "@angular/compiler-cli": "17.0.2", "@angular/language-service": "17.0.2", "@nx/cypress": "17.1.2", + "@nx/eslint": "17.1.2", "@nx/eslint-plugin": "17.1.2", "@nx/jest": "17.1.2", "@nx/workspace": "17.1.2", "@schematics/angular": "17.0.0", "@types/jest": "29.4.4", + "@types/lodash": "^4.14.201", "@types/node": "14.14.33", "@typescript-eslint/eslint-plugin": "6.11.0", "@typescript-eslint/parser": "6.11.0", @@ -62,7 +65,6 @@ "prettier": "2.7.1", "ts-jest": "29.1.0", "ts-node": "10.9.1", - "typescript": "5.2.2", - "@nx/eslint": "17.1.2" + "typescript": "5.2.2" } }