mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
✨ feat(core-connectivity): add network status service library
- Scaffold new core-connectivity library with Nx generator - Migrate NetworkStatusService from isa-app to shared library - Refactor service to use fromEvent/merge with shareReplay - Add NetworkStatus type and injectNetworkStatus signal helper - Update all consumers to use @isa/core/connectivity imports - Add comprehensive unit tests (9 tests) - Configure Vitest with JUnit/Cobertura reporters - Update library-reference.md (74 → 75 libraries)
This commit is contained in:
@@ -11,7 +11,7 @@ import { Config } from '@core/config';
|
||||
import { ComponentPortal } from '@angular/cdk/portal';
|
||||
import { ScanditOverlayComponent } from './scandit-overlay.component';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { injectNetworkStatus$ } from 'apps/isa-app/src/app/services/network-status.service';
|
||||
import { injectNetworkStatus$ } from '@isa/core/connectivity';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
|
||||
@Injectable()
|
||||
|
||||
@@ -24,8 +24,7 @@ import { IsaLogProvider } from './providers';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { AuthService, LoginStrategy } from '@core/auth';
|
||||
import { UiMessageModalComponent, UiModalService } from '@ui/modal';
|
||||
import { injectOnline$ } from './services/network-status.service';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
import { injectNetworkStatus } from '@isa/core/connectivity';
|
||||
import { animate, style, transition, trigger } from '@angular/animations';
|
||||
|
||||
@Component({
|
||||
@@ -50,7 +49,7 @@ import { animate, style, transition, trigger } from '@angular/animations';
|
||||
export class AppComponent implements OnInit {
|
||||
readonly injector = inject(Injector);
|
||||
|
||||
$online = toSignal(injectOnline$());
|
||||
$networkStatus = injectNetworkStatus();
|
||||
|
||||
$offlineBannerVisible = signal(false);
|
||||
|
||||
@@ -59,7 +58,8 @@ export class AppComponent implements OnInit {
|
||||
private onlineBannerDismissTimeout: any;
|
||||
|
||||
onlineEffects = effect(() => {
|
||||
const online = this.$online();
|
||||
const status = this.$networkStatus();
|
||||
const online = status === 'online';
|
||||
const offlineBannerVisible = this.$offlineBannerVisible();
|
||||
|
||||
untracked(() => {
|
||||
|
||||
@@ -67,7 +67,7 @@ import {
|
||||
matWifi,
|
||||
matWifiOff,
|
||||
} from '@ng-icons/material-icons/baseline';
|
||||
import { NetworkStatusService } from './services/network-status.service';
|
||||
import { NetworkStatusService } from '@isa/core/connectivity';
|
||||
import { debounceTime, filter, firstValueFrom, switchMap } from 'rxjs';
|
||||
import { provideMatomo } from 'ngx-matomo-client';
|
||||
import { withRouter, withRouteData } from 'ngx-matomo-client';
|
||||
@@ -106,7 +106,8 @@ export function _appInitializerFactory(config: Config, injector: Injector) {
|
||||
let online = false;
|
||||
const networkStatus = injector.get(NetworkStatusService);
|
||||
while (!online) {
|
||||
online = await firstValueFrom(networkStatus.online$);
|
||||
const status = await firstValueFrom(networkStatus.status$);
|
||||
online = status === 'online';
|
||||
|
||||
if (!online) {
|
||||
logger.warn('Waiting for network connection');
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ScanAdapterService } from '@adapter/scan';
|
||||
import { AuthService as IsaAuthService } from '@generated/swagger/isa-api';
|
||||
import { UiConfirmModalComponent, UiErrorModalComponent, UiModalResult, UiModalService } from '@ui/modal';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { injectNetworkStatus$ } from '../services/network-status.service';
|
||||
import { injectNetworkStatus$ } from '@isa/core/connectivity';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
import { from, NEVER, Observable, throwError } from 'rxjs';
|
||||
import { catchError, filter, mergeMap, takeUntil } from 'rxjs/operators';
|
||||
import { AuthService, LoginStrategy } from '@core/auth';
|
||||
import { injectOnline$ } from '../services/network-status.service';
|
||||
import { injectNetworkStatus$ } from '@isa/core/connectivity';
|
||||
import { logger } from '@isa/core/logging';
|
||||
|
||||
@Injectable()
|
||||
@@ -17,7 +17,7 @@ export class HttpErrorInterceptor implements HttpInterceptor {
|
||||
#logger = logger(() => ({
|
||||
'http-interceptor': 'HttpErrorInterceptor',
|
||||
}));
|
||||
#offline$ = injectOnline$().pipe(filter((online) => !online));
|
||||
#offline$ = injectNetworkStatus$().pipe(filter((status) => status === 'offline'));
|
||||
#injector = inject(Injector);
|
||||
#auth = inject(AuthService);
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export * from './network-status.service';
|
||||
@@ -1,25 +0,0 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { map, Observable } from 'rxjs';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class NetworkStatusService {
|
||||
online$ = new Observable<boolean>((subscriber) => {
|
||||
const handler = () => subscriber.next(navigator.onLine);
|
||||
|
||||
window.addEventListener('online', handler);
|
||||
window.addEventListener('offline', handler);
|
||||
|
||||
handler();
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('online', handler);
|
||||
window.removeEventListener('offline', handler);
|
||||
};
|
||||
});
|
||||
|
||||
status$ = this.online$.pipe(map((online) => (online ? 'online' : 'offline')));
|
||||
}
|
||||
|
||||
export const injectNetworkStatus$ = () => inject(NetworkStatusService).status$;
|
||||
|
||||
export const injectOnline$ = () => inject(NetworkStatusService).online$;
|
||||
@@ -3,7 +3,7 @@ import { inject, Injectable } from '@angular/core';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { UiConfirmModalComponent, UiModalResult, UiModalService } from '@ui/modal';
|
||||
import { injectNetworkStatus$ } from '../../app/services';
|
||||
import { injectNetworkStatus$ } from '@isa/core/connectivity';
|
||||
import { AuthService } from './auth.service';
|
||||
import { AuthService as IsaAuthService } from '@generated/swagger/isa-api';
|
||||
import { firstValueFrom, lastValueFrom } from 'rxjs';
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
> **Last Updated:** 2025-11-28
|
||||
> **Angular Version:** 20.3.6
|
||||
> **Nx Version:** 21.3.2
|
||||
> **Total Libraries:** 74
|
||||
> **Total Libraries:** 75
|
||||
|
||||
All 74 libraries in the monorepo have comprehensive README.md documentation located at `libs/[domain]/[layer]/[feature]/README.md`.
|
||||
All 75 libraries in the monorepo have comprehensive README.md documentation located at `libs/[domain]/[layer]/[feature]/README.md`.
|
||||
|
||||
**IMPORTANT: Always use the `docs-researcher` subagent** to retrieve and analyze library documentation. This keeps the main context clean and prevents pollution.
|
||||
|
||||
@@ -85,13 +85,18 @@ A comprehensive print management library for Angular applications providing prin
|
||||
|
||||
---
|
||||
|
||||
## Core Libraries (6 libraries)
|
||||
## Core Libraries (7 libraries)
|
||||
|
||||
### `@isa/core/auth`
|
||||
Type-safe role-based authorization utilities with Angular signals integration for the ISA Frontend application.
|
||||
|
||||
**Location:** `libs/core/auth/`
|
||||
|
||||
### `@isa/core/connectivity`
|
||||
Network connectivity status service providing reactive online/offline observables for monitoring network state across the application.
|
||||
|
||||
**Location:** `libs/core/connectivity/`
|
||||
|
||||
### `@isa/core/config`
|
||||
A lightweight, type-safe configuration management system for Angular applications with runtime validation and nested object access.
|
||||
|
||||
|
||||
7
libs/core/connectivity/README.md
Normal file
7
libs/core/connectivity/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# core-connectivity
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `nx test core-connectivity` to execute the unit tests.
|
||||
34
libs/core/connectivity/eslint.config.cjs
Normal file
34
libs/core/connectivity/eslint.config.cjs
Normal file
@@ -0,0 +1,34 @@
|
||||
const nx = require('@nx/eslint-plugin');
|
||||
const baseConfig = require('../../../eslint.config.js');
|
||||
|
||||
module.exports = [
|
||||
...baseConfig,
|
||||
...nx.configs['flat/angular'],
|
||||
...nx.configs['flat/angular-template'],
|
||||
{
|
||||
files: ['**/*.ts'],
|
||||
rules: {
|
||||
'@angular-eslint/directive-selector': [
|
||||
'error',
|
||||
{
|
||||
type: 'attribute',
|
||||
prefix: 'core',
|
||||
style: 'camelCase',
|
||||
},
|
||||
],
|
||||
'@angular-eslint/component-selector': [
|
||||
'error',
|
||||
{
|
||||
type: 'element',
|
||||
prefix: 'core',
|
||||
style: 'kebab-case',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.html'],
|
||||
// Override or add rules here
|
||||
rules: {},
|
||||
},
|
||||
];
|
||||
20
libs/core/connectivity/project.json
Normal file
20
libs/core/connectivity/project.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "core-connectivity",
|
||||
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "libs/core/connectivity/src",
|
||||
"prefix": "core",
|
||||
"projectType": "library",
|
||||
"tags": ["scope:core", "type:core"],
|
||||
"targets": {
|
||||
"test": {
|
||||
"executor": "@nx/vite:test",
|
||||
"outputs": ["{options.reportsDirectory}"],
|
||||
"options": {
|
||||
"reportsDirectory": "../../../coverage/libs/core/connectivity"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/eslint:lint"
|
||||
}
|
||||
}
|
||||
}
|
||||
6
libs/core/connectivity/src/index.ts
Normal file
6
libs/core/connectivity/src/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export {
|
||||
NetworkStatusService,
|
||||
NetworkStatus,
|
||||
injectNetworkStatus$,
|
||||
injectNetworkStatus,
|
||||
} from './lib/network-status.service';
|
||||
137
libs/core/connectivity/src/lib/network-status.service.spec.ts
Normal file
137
libs/core/connectivity/src/lib/network-status.service.spec.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { firstValueFrom, take, toArray } from 'rxjs';
|
||||
import {
|
||||
NetworkStatusService,
|
||||
injectNetworkStatus$,
|
||||
injectNetworkStatus,
|
||||
} from './network-status.service';
|
||||
|
||||
describe('NetworkStatusService', () => {
|
||||
let service: NetworkStatusService;
|
||||
let originalNavigatorOnLine: boolean;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(NetworkStatusService);
|
||||
originalNavigatorOnLine = navigator.onLine;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
Object.defineProperty(navigator, 'onLine', {
|
||||
value: originalNavigatorOnLine,
|
||||
writable: true,
|
||||
configurable: true,
|
||||
});
|
||||
});
|
||||
|
||||
const setNavigatorOnLine = (value: boolean) => {
|
||||
Object.defineProperty(navigator, 'onLine', {
|
||||
value,
|
||||
writable: true,
|
||||
configurable: true,
|
||||
});
|
||||
};
|
||||
|
||||
describe('status$', () => {
|
||||
it('should emit initial status based on navigator.onLine', async () => {
|
||||
setNavigatorOnLine(true);
|
||||
const newService = TestBed.inject(NetworkStatusService);
|
||||
|
||||
const status = await firstValueFrom(newService.status$);
|
||||
|
||||
expect(status).toBe('online');
|
||||
});
|
||||
|
||||
it('should emit "offline" when navigator.onLine is false', async () => {
|
||||
setNavigatorOnLine(false);
|
||||
|
||||
const status = await firstValueFrom(service.status$);
|
||||
|
||||
expect(status).toBe('offline');
|
||||
});
|
||||
|
||||
it('should emit "online" when online event is dispatched', async () => {
|
||||
setNavigatorOnLine(false);
|
||||
|
||||
const statusPromise = firstValueFrom(service.status$.pipe(take(2), toArray()));
|
||||
|
||||
setNavigatorOnLine(true);
|
||||
window.dispatchEvent(new Event('online'));
|
||||
|
||||
const statuses = await statusPromise;
|
||||
|
||||
expect(statuses).toContain('online');
|
||||
});
|
||||
|
||||
it('should emit "offline" when offline event is dispatched', async () => {
|
||||
setNavigatorOnLine(true);
|
||||
|
||||
const statusPromise = firstValueFrom(service.status$.pipe(take(2), toArray()));
|
||||
|
||||
setNavigatorOnLine(false);
|
||||
window.dispatchEvent(new Event('offline'));
|
||||
|
||||
const statuses = await statusPromise;
|
||||
|
||||
expect(statuses).toContain('offline');
|
||||
});
|
||||
|
||||
it('should share the same observable instance (shareReplay)', async () => {
|
||||
const subscription1Values: string[] = [];
|
||||
const subscription2Values: string[] = [];
|
||||
|
||||
const sub1 = service.status$.subscribe((v) => subscription1Values.push(v));
|
||||
const sub2 = service.status$.subscribe((v) => subscription2Values.push(v));
|
||||
|
||||
// Both subscribers should receive the same initial value
|
||||
expect(subscription1Values.length).toBeGreaterThan(0);
|
||||
expect(subscription2Values.length).toBeGreaterThan(0);
|
||||
expect(subscription1Values[0]).toBe(subscription2Values[0]);
|
||||
|
||||
sub1.unsubscribe();
|
||||
sub2.unsubscribe();
|
||||
});
|
||||
|
||||
it('should replay last value to new subscribers', async () => {
|
||||
// First subscriber gets the value
|
||||
const firstValue = await firstValueFrom(service.status$);
|
||||
|
||||
// Second subscriber should get the same replayed value
|
||||
const secondValue = await firstValueFrom(service.status$);
|
||||
|
||||
expect(firstValue).toBe(secondValue);
|
||||
});
|
||||
});
|
||||
|
||||
describe('injectNetworkStatus$', () => {
|
||||
it('should return status$ observable from service', () => {
|
||||
TestBed.runInInjectionContext(() => {
|
||||
const status$ = injectNetworkStatus$();
|
||||
|
||||
expect(status$).toBe(service.status$);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('injectNetworkStatus', () => {
|
||||
it('should return a signal with current network status', () => {
|
||||
setNavigatorOnLine(true);
|
||||
|
||||
TestBed.runInInjectionContext(() => {
|
||||
const statusSignal = injectNetworkStatus();
|
||||
|
||||
expect(statusSignal()).toBe('online');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a signal that reflects offline status', () => {
|
||||
setNavigatorOnLine(false);
|
||||
|
||||
TestBed.runInInjectionContext(() => {
|
||||
const statusSignal = injectNetworkStatus();
|
||||
|
||||
expect(statusSignal()).toBe('offline');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
28
libs/core/connectivity/src/lib/network-status.service.ts
Normal file
28
libs/core/connectivity/src/lib/network-status.service.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
import {
|
||||
map,
|
||||
Observable,
|
||||
fromEvent,
|
||||
merge,
|
||||
startWith,
|
||||
shareReplay,
|
||||
} from 'rxjs';
|
||||
|
||||
export type NetworkStatus = 'online' | 'offline';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class NetworkStatusService {
|
||||
readonly status$: Observable<NetworkStatus> = merge(
|
||||
fromEvent(window, 'online'),
|
||||
fromEvent(window, 'offline'),
|
||||
).pipe(
|
||||
startWith(null), // emit immediately
|
||||
map((): NetworkStatus => (navigator.onLine ? 'online' : 'offline')),
|
||||
shareReplay({ bufferSize: 1, refCount: true }),
|
||||
);
|
||||
}
|
||||
|
||||
export const injectNetworkStatus$ = () => inject(NetworkStatusService).status$;
|
||||
|
||||
export const injectNetworkStatus = () => toSignal(injectNetworkStatus$());
|
||||
13
libs/core/connectivity/src/test-setup.ts
Normal file
13
libs/core/connectivity/src/test-setup.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import '@angular/compiler';
|
||||
import '@analogjs/vitest-angular/setup-zone';
|
||||
|
||||
import {
|
||||
BrowserTestingModule,
|
||||
platformBrowserTesting,
|
||||
} from '@angular/platform-browser/testing';
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
|
||||
getTestBed().initTestEnvironment(
|
||||
BrowserTestingModule,
|
||||
platformBrowserTesting(),
|
||||
);
|
||||
30
libs/core/connectivity/tsconfig.json
Normal file
30
libs/core/connectivity/tsconfig.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"importHelpers": true,
|
||||
"moduleResolution": "bundler",
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "preserve"
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
"strictInjectionParameters": true,
|
||||
"strictInputAccessModifiers": true,
|
||||
"typeCheckHostBindings": true,
|
||||
"strictTemplates": true
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
27
libs/core/connectivity/tsconfig.lib.json
Normal file
27
libs/core/connectivity/tsconfig.lib.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../../dist/out-tsc",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"inlineSources": true,
|
||||
"types": []
|
||||
},
|
||||
"exclude": [
|
||||
"src/**/*.spec.ts",
|
||||
"src/test-setup.ts",
|
||||
"jest.config.ts",
|
||||
"src/**/*.test.ts",
|
||||
"vite.config.ts",
|
||||
"vite.config.mts",
|
||||
"vitest.config.ts",
|
||||
"vitest.config.mts",
|
||||
"src/**/*.test.tsx",
|
||||
"src/**/*.spec.tsx",
|
||||
"src/**/*.test.js",
|
||||
"src/**/*.spec.js",
|
||||
"src/**/*.test.jsx",
|
||||
"src/**/*.spec.jsx"
|
||||
],
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
29
libs/core/connectivity/tsconfig.spec.json
Normal file
29
libs/core/connectivity/tsconfig.spec.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../../dist/out-tsc",
|
||||
"types": [
|
||||
"vitest/globals",
|
||||
"vitest/importMeta",
|
||||
"vite/client",
|
||||
"node",
|
||||
"vitest"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"vite.config.ts",
|
||||
"vite.config.mts",
|
||||
"vitest.config.ts",
|
||||
"vitest.config.mts",
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.test.tsx",
|
||||
"src/**/*.spec.tsx",
|
||||
"src/**/*.test.js",
|
||||
"src/**/*.spec.js",
|
||||
"src/**/*.test.jsx",
|
||||
"src/**/*.spec.jsx",
|
||||
"src/**/*.d.ts"
|
||||
],
|
||||
"files": ["src/test-setup.ts"]
|
||||
}
|
||||
29
libs/core/connectivity/vite.config.mts
Normal file
29
libs/core/connectivity/vite.config.mts
Normal file
@@ -0,0 +1,29 @@
|
||||
/// <reference types='vitest' />
|
||||
import { defineConfig } from 'vite';
|
||||
import angular from '@analogjs/vite-plugin-angular';
|
||||
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||
import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
|
||||
|
||||
export default
|
||||
// @ts-expect-error - Vitest reporter tuple types have complex inference issues
|
||||
defineConfig(() => ({
|
||||
root: __dirname,
|
||||
cacheDir: '../../../node_modules/.vite/libs/core/connectivity',
|
||||
plugins: [angular(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
|
||||
test: {
|
||||
watch: false,
|
||||
globals: true,
|
||||
environment: 'jsdom',
|
||||
include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
||||
setupFiles: ['src/test-setup.ts'],
|
||||
reporters: [
|
||||
'default',
|
||||
['junit', { outputFile: '../../../testresults/junit-core-connectivity.xml' }],
|
||||
],
|
||||
coverage: {
|
||||
reportsDirectory: '../../../coverage/libs/core/connectivity',
|
||||
provider: 'v8' as const,
|
||||
reporter: ['text', 'cobertura'],
|
||||
},
|
||||
},
|
||||
}));
|
||||
@@ -70,6 +70,7 @@
|
||||
],
|
||||
"@isa/core/auth": ["libs/core/auth/src/index.ts"],
|
||||
"@isa/core/config": ["libs/core/config/src/index.ts"],
|
||||
"@isa/core/connectivity": ["libs/core/connectivity/src/index.ts"],
|
||||
"@isa/core/logging": ["libs/core/logging/src/index.ts"],
|
||||
"@isa/core/navigation": ["libs/core/navigation/src/index.ts"],
|
||||
"@isa/core/storage": ["libs/core/storage/src/index.ts"],
|
||||
|
||||
Reference in New Issue
Block a user