Browse Source

Merge remote-tracking branch 'origin/main' into feature/enable-strict-null-checks-in-ui

pull/6264/head
Kenrick Tandrian 2 weeks ago
parent
commit
e0da8bb957
  1. 1
      CHANGELOG.md
  2. 47
      libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.component.ts
  3. 3
      libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html
  4. 2
      libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/interfaces/interfaces.ts
  5. 24
      libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html
  6. 60
      libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.spec.ts
  7. 133
      libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts
  8. 8
      package-lock.json
  9. 5
      package.json

1
CHANGELOG.md

@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Upgraded `marked` from version `17.0.1` to `17.0.2`
- Upgraded `ngx-markdown` from version `21.0.1` to `21.1.0` - Upgraded `ngx-markdown` from version `21.0.1` to `21.1.0`
## 2.239.0 - 2026-02-15 ## 2.239.0 - 2026-02-15

47
libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.component.ts

@ -5,10 +5,12 @@ import {
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
CUSTOM_ELEMENTS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA,
Inject, DestroyRef,
OnDestroy, OnInit,
OnInit inject,
signal
} from '@angular/core'; } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { DateAdapter, MAT_DATE_LOCALE } from '@angular/material/core'; import { DateAdapter, MAT_DATE_LOCALE } from '@angular/material/core';
@ -23,7 +25,6 @@ import { MatInputModule } from '@angular/material/input';
import { IonIcon } from '@ionic/angular/standalone'; import { IonIcon } from '@ionic/angular/standalone';
import { addIcons } from 'ionicons'; import { addIcons } from 'ionicons';
import { calendarClearOutline, refreshOutline } from 'ionicons/icons'; import { calendarClearOutline, refreshOutline } from 'ionicons/icons';
import { Subject, takeUntil } from 'rxjs';
import { HistoricalMarketDataEditorDialogParams } from './interfaces/interfaces'; import { HistoricalMarketDataEditorDialogParams } from './interfaces/interfaces';
@ -45,26 +46,27 @@ import { HistoricalMarketDataEditorDialogParams } from './interfaces/interfaces'
styleUrls: ['./historical-market-data-editor-dialog.scss'], styleUrls: ['./historical-market-data-editor-dialog.scss'],
templateUrl: 'historical-market-data-editor-dialog.html' templateUrl: 'historical-market-data-editor-dialog.html'
}) })
export class GfHistoricalMarketDataEditorDialogComponent export class GfHistoricalMarketDataEditorDialogComponent implements OnInit {
implements OnDestroy, OnInit public readonly data =
{ inject<HistoricalMarketDataEditorDialogParams>(MAT_DIALOG_DATA);
private unsubscribeSubject = new Subject<void>();
protected readonly marketPrice = signal(this.data.marketPrice);
private readonly destroyRef = inject(DestroyRef);
private readonly locale =
this.data.user.settings.locale ?? inject<string>(MAT_DATE_LOCALE);
public constructor( public constructor(
private adminService: AdminService, private adminService: AdminService,
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
@Inject(MAT_DIALOG_DATA)
public data: HistoricalMarketDataEditorDialogParams,
private dataService: DataService, private dataService: DataService,
private dateAdapter: DateAdapter<any>, private dateAdapter: DateAdapter<Date, string>,
public dialogRef: MatDialogRef<GfHistoricalMarketDataEditorDialogComponent>, public dialogRef: MatDialogRef<GfHistoricalMarketDataEditorDialogComponent>
@Inject(MAT_DATE_LOCALE) private locale: string
) { ) {
addIcons({ calendarClearOutline, refreshOutline }); addIcons({ calendarClearOutline, refreshOutline });
} }
public ngOnInit() { public ngOnInit() {
this.locale = this.data.user?.settings?.locale;
this.dateAdapter.setLocale(this.locale); this.dateAdapter.setLocale(this.locale);
} }
@ -79,15 +81,19 @@ export class GfHistoricalMarketDataEditorDialogComponent
dateString: this.data.dateString, dateString: this.data.dateString,
symbol: this.data.symbol symbol: this.data.symbol
}) })
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(({ marketPrice }) => { .subscribe(({ marketPrice }) => {
this.data.marketPrice = marketPrice; this.marketPrice.set(marketPrice);
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
}); });
} }
public onUpdate() { public onUpdate() {
if (this.marketPrice() === undefined) {
return;
}
this.dataService this.dataService
.postMarketData({ .postMarketData({
dataSource: this.data.dataSource, dataSource: this.data.dataSource,
@ -95,20 +101,15 @@ export class GfHistoricalMarketDataEditorDialogComponent
marketData: [ marketData: [
{ {
date: this.data.dateString, date: this.data.dateString,
marketPrice: this.data.marketPrice marketPrice: this.marketPrice()
} }
] ]
}, },
symbol: this.data.symbol symbol: this.data.symbol
}) })
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(() => { .subscribe(() => {
this.dialogRef.close({ withRefresh: true }); this.dialogRef.close({ withRefresh: true });
}); });
} }
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
} }

3
libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html

@ -28,7 +28,8 @@
matInput matInput
name="marketPrice" name="marketPrice"
type="number" type="number"
[(ngModel)]="data.marketPrice" [ngModel]="marketPrice()"
(ngModelChange)="marketPrice.set($event)"
/> />
<span class="ml-2" matTextSuffix>{{ data.currency }}</span> <span class="ml-2" matTextSuffix>{{ data.currency }}</span>
</mat-form-field> </mat-form-field>

2
libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/interfaces/interfaces.ts

@ -6,7 +6,7 @@ export interface HistoricalMarketDataEditorDialogParams {
currency: string; currency: string;
dataSource: DataSource; dataSource: DataSource;
dateString: string; dateString: string;
marketPrice: number; marketPrice?: number;
symbol: string; symbol: string;
user: User; user: User;
} }

24
libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html

@ -3,28 +3,24 @@
<div class="d-flex"> <div class="d-flex">
<div class="date mr-1 text-nowrap">{{ itemByMonth.key }}</div> <div class="date mr-1 text-nowrap">{{ itemByMonth.key }}</div>
<div class="align-items-center d-flex flex-grow-1 px-1"> <div class="align-items-center d-flex flex-grow-1 px-1">
@for (dayItem of days; track dayItem; let i = $index) { @for (day of days; track day) {
<div <div
class="day" class="day"
[ngClass]="{ [ngClass]="{
'cursor-pointer valid': isDateOfInterest( 'cursor-pointer valid': isDateOfInterest(
`${itemByMonth.key}-${i + 1 < 10 ? `0${i + 1}` : i + 1}` `${itemByMonth.key}-${formatDay(day)}`
), ),
available: available:
marketDataByMonth[itemByMonth.key][ marketDataByMonth[itemByMonth.key][formatDay(day)]?.marketPrice,
i + 1 < 10 ? `0${i + 1}` : i + 1 today: isToday(`${itemByMonth.key}-${formatDay(day)}`)
]?.marketPrice,
today: isToday(
`${itemByMonth.key}-${i + 1 < 10 ? `0${i + 1}` : i + 1}`
)
}" }"
[title]=" [title]="
(`${itemByMonth.key}-${i + 1 < 10 ? `0${i + 1}` : i + 1}` (`${itemByMonth.key}-${formatDay(day)}`
| date: defaultDateFormat) ?? '' | date: defaultDateFormat()) ?? ''
" "
(click)=" (click)="
onOpenMarketDataDetail({ onOpenMarketDataDetail({
day: i + 1 < 10 ? `0${i + 1}` : i + 1, day: formatDay(day),
yearMonth: itemByMonth.key yearMonth: itemByMonth.key
}) })
" "
@ -61,10 +57,10 @@
mat-flat-button mat-flat-button
type="button" type="button"
[disabled]=" [disabled]="
!historicalDataForm.controls['historicalData']?.controls['csvString'] !historicalDataForm.controls.historicalData.controls.csvString
.touched || .touched ||
historicalDataForm.controls['historicalData']?.controls['csvString'] historicalDataForm.controls.historicalData.controls.csvString
?.value === '' .value === ''
" "
(click)="onImportHistoricalData()" (click)="onImportHistoricalData()"
> >

60
libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.spec.ts

@ -0,0 +1,60 @@
import { DataService } from '@ghostfolio/ui/services';
import { signal } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FormBuilder } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog';
import { MatSnackBar } from '@angular/material/snack-bar';
import { DeviceDetectorService } from 'ngx-device-detector';
import { GfHistoricalMarketDataEditorComponent } from './historical-market-data-editor.component';
jest.mock(
'./historical-market-data-editor-dialog/historical-market-data-editor-dialog.component',
() => ({
GfHistoricalMarketDataEditorDialogComponent: class {}
})
);
describe('GfHistoricalMarketDataEditorComponent', () => {
let component: GfHistoricalMarketDataEditorComponent;
let fixture: ComponentFixture<GfHistoricalMarketDataEditorComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [GfHistoricalMarketDataEditorComponent],
providers: [
FormBuilder,
{ provide: DataService, useValue: {} },
{
provide: DeviceDetectorService,
useValue: {
deviceInfo: signal({ deviceType: 'desktop' })
}
},
{ provide: MatDialog, useValue: {} },
{ provide: MatSnackBar, useValue: {} }
]
}).compileComponents();
fixture = TestBed.createComponent(GfHistoricalMarketDataEditorComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
describe('formatDay', () => {
it('should pad single digit days with zero', () => {
expect(component.formatDay(1)).toBe('01');
expect(component.formatDay(9)).toBe('09');
});
it('should not pad double digit days', () => {
expect(component.formatDay(10)).toBe('10');
expect(component.formatDay(31)).toBe('31');
});
});
});

133
libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts

@ -8,16 +8,21 @@ import { LineChartItem, User } from '@ghostfolio/common/interfaces';
import { DataService } from '@ghostfolio/ui/services'; import { DataService } from '@ghostfolio/ui/services';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import type { HttpErrorResponse } from '@angular/common/http';
import { import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
Component, Component,
computed,
DestroyRef,
EventEmitter, EventEmitter,
inject,
input,
Input, Input,
OnChanges, OnChanges,
OnDestroy,
OnInit, OnInit,
Output Output
} from '@angular/core'; } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormBuilder, ReactiveFormsModule } from '@angular/forms'; import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
@ -40,7 +45,7 @@ import { first, last } from 'lodash';
import ms from 'ms'; import ms from 'ms';
import { DeviceDetectorService } from 'ngx-device-detector'; import { DeviceDetectorService } from 'ngx-device-detector';
import { parse as csvToJson } from 'papaparse'; import { parse as csvToJson } from 'papaparse';
import { EMPTY, Subject, takeUntil } from 'rxjs'; import { EMPTY } from 'rxjs';
import { catchError } from 'rxjs/operators'; import { catchError } from 'rxjs/operators';
import { GfHistoricalMarketDataEditorDialogComponent } from './historical-market-data-editor-dialog/historical-market-data-editor-dialog.component'; import { GfHistoricalMarketDataEditorDialogComponent } from './historical-market-data-editor-dialog/historical-market-data-editor-dialog.component';
@ -54,74 +59,80 @@ import { HistoricalMarketDataEditorDialogParams } from './historical-market-data
templateUrl: './historical-market-data-editor.component.html' templateUrl: './historical-market-data-editor.component.html'
}) })
export class GfHistoricalMarketDataEditorComponent export class GfHistoricalMarketDataEditorComponent
implements OnChanges, OnDestroy, OnInit implements OnChanges, OnInit
{ {
private static readonly HISTORICAL_DATA_TEMPLATE = `date;marketPrice\n${format(
new Date(),
DATE_FORMAT
)};123.45`;
@Input() currency: string; @Input() currency: string;
@Input() dataSource: DataSource; @Input() dataSource: DataSource;
@Input() dateOfFirstActivity: string; @Input() dateOfFirstActivity: string;
@Input() locale = getLocale();
@Input() marketData: MarketData[];
@Input() symbol: string; @Input() symbol: string;
@Input() user: User; @Input() user: User;
@Output() marketDataChanged = new EventEmitter<boolean>(); @Output() marketDataChanged = new EventEmitter<boolean>();
public days = Array(31);
public defaultDateFormat: string;
public deviceType: string;
public historicalDataForm = this.formBuilder.group({ public historicalDataForm = this.formBuilder.group({
historicalData: this.formBuilder.group({ historicalData: this.formBuilder.group({
csvString: '' csvString: ''
}) })
}); });
public historicalDataItems: LineChartItem[];
public marketDataByMonth: { public marketDataByMonth: {
[yearMonth: string]: { [yearMonth: string]: {
[day: string]: Pick<MarketData, 'date' | 'marketPrice'> & { day: number }; [day: string]: {
date: Date;
day: number;
marketPrice?: number;
};
}; };
} = {}; } = {};
private static readonly HISTORICAL_DATA_TEMPLATE = `date;marketPrice\n${format( public readonly locale = input(getLocale());
new Date(), public readonly marketData = input.required<MarketData[]>();
DATE_FORMAT
)};123.45`; protected readonly days = Array.from({ length: 31 }, (_, i) => i + 1);
protected readonly defaultDateFormat = computed(() =>
private unsubscribeSubject = new Subject<void>(); getDateFormatString(this.locale())
);
private readonly destroyRef = inject(DestroyRef);
private readonly deviceDetectorService = inject(DeviceDetectorService);
private readonly deviceType = computed(
() => this.deviceDetectorService.deviceInfo().deviceType
);
private readonly historicalDataItems = computed<LineChartItem[]>(() =>
this.marketData().map(({ date, marketPrice }) => {
return {
date: format(date, DATE_FORMAT),
value: marketPrice
};
})
);
public constructor( public constructor(
private dataService: DataService, private dataService: DataService,
private deviceService: DeviceDetectorService,
private dialog: MatDialog, private dialog: MatDialog,
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
private snackBar: MatSnackBar private snackBar: MatSnackBar
) { ) {}
this.deviceType = this.deviceService.getDeviceInfo().deviceType;
}
public ngOnInit() { public ngOnInit() {
this.initializeHistoricalDataForm(); this.initializeHistoricalDataForm();
} }
public ngOnChanges() { public ngOnChanges() {
this.defaultDateFormat = getDateFormatString(this.locale);
this.historicalDataItems = this.marketData.map(({ date, marketPrice }) => {
return {
date: format(date, DATE_FORMAT),
value: marketPrice
};
});
if (this.dateOfFirstActivity) { if (this.dateOfFirstActivity) {
let date = parseISO(this.dateOfFirstActivity); let date = parseISO(this.dateOfFirstActivity);
const missingMarketData: Partial<MarketData>[] = []; const missingMarketData: { date: Date; marketPrice?: number }[] = [];
if (this.historicalDataItems?.[0]?.date) { if (this.historicalDataItems()?.[0]?.date) {
while ( while (
isBefore( isBefore(
date, date,
parse(this.historicalDataItems[0].date, DATE_FORMAT, new Date()) parse(this.historicalDataItems()[0].date, DATE_FORMAT, new Date())
) )
) { ) {
missingMarketData.push({ missingMarketData.push({
@ -133,9 +144,10 @@ export class GfHistoricalMarketDataEditorComponent
} }
} }
const marketDataItems = [...missingMarketData, ...this.marketData]; const marketDataItems = [...missingMarketData, ...this.marketData()];
if (!isToday(last(marketDataItems)?.date)) { const lastDate = last(marketDataItems)?.date;
if (!lastDate || !isToday(lastDate)) {
marketDataItems.push({ date: new Date() }); marketDataItems.push({ date: new Date() });
} }
@ -160,25 +172,34 @@ export class GfHistoricalMarketDataEditorComponent
// Fill up missing months // Fill up missing months
const dates = Object.keys(this.marketDataByMonth).sort(); const dates = Object.keys(this.marketDataByMonth).sort();
const startDateString = first(dates);
const startDate = min([ const startDate = min([
parseISO(this.dateOfFirstActivity), parseISO(this.dateOfFirstActivity),
parseISO(first(dates)) ...(startDateString ? [parseISO(startDateString)] : [])
]); ]);
const endDate = parseISO(last(dates)); const endDateString = last(dates);
let currentDate = startDate; if (endDateString) {
const endDate = parseISO(endDateString);
while (isBefore(currentDate, endDate)) { let currentDate = startDate;
const key = format(currentDate, 'yyyy-MM');
if (!this.marketDataByMonth[key]) {
this.marketDataByMonth[key] = {};
}
currentDate = addMonths(currentDate, 1); while (isBefore(currentDate, endDate)) {
const key = format(currentDate, 'yyyy-MM');
if (!this.marketDataByMonth[key]) {
this.marketDataByMonth[key] = {};
}
currentDate = addMonths(currentDate, 1);
}
} }
} }
} }
public formatDay(day: number): string {
return day < 10 ? `0${day}` : `${day}`;
}
public isDateOfInterest(aDateString: string) { public isDateOfInterest(aDateString: string) {
// Date is valid and in the past // Date is valid and in the past
const date = parse(aDateString, DATE_FORMAT, new Date()); const date = parse(aDateString, DATE_FORMAT, new Date());
@ -201,7 +222,8 @@ export class GfHistoricalMarketDataEditorComponent
const dialogRef = this.dialog.open< const dialogRef = this.dialog.open<
GfHistoricalMarketDataEditorDialogComponent, GfHistoricalMarketDataEditorDialogComponent,
HistoricalMarketDataEditorDialogParams HistoricalMarketDataEditorDialogParams,
{ withRefresh: boolean }
>(GfHistoricalMarketDataEditorDialogComponent, { >(GfHistoricalMarketDataEditorDialogComponent, {
data: { data: {
marketPrice, marketPrice,
@ -211,13 +233,13 @@ export class GfHistoricalMarketDataEditorComponent
symbol: this.symbol, symbol: this.symbol,
user: this.user user: this.user
}, },
height: this.deviceType === 'mobile' ? '98vh' : '80vh', height: this.deviceType() === 'mobile' ? '98vh' : '80vh',
width: this.deviceType === 'mobile' ? '100vw' : '50rem' width: this.deviceType() === 'mobile' ? '100vw' : '50rem'
}); });
dialogRef dialogRef
.afterClosed() .afterClosed()
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(({ withRefresh } = { withRefresh: false }) => { .subscribe(({ withRefresh } = { withRefresh: false }) => {
this.marketDataChanged.emit(withRefresh); this.marketDataChanged.emit(withRefresh);
}); });
@ -225,15 +247,15 @@ export class GfHistoricalMarketDataEditorComponent
public onImportHistoricalData() { public onImportHistoricalData() {
try { try {
const marketData = csvToJson( const marketData = csvToJson<UpdateMarketDataDto>(
this.historicalDataForm.controls['historicalData'].controls['csvString'] this.historicalDataForm.controls.historicalData.controls.csvString
.value, .value ?? '',
{ {
dynamicTyping: true, dynamicTyping: true,
header: true, header: true,
skipEmptyLines: true skipEmptyLines: true
} }
).data as UpdateMarketDataDto[]; ).data;
this.dataService this.dataService
.postMarketData({ .postMarketData({
@ -244,13 +266,13 @@ export class GfHistoricalMarketDataEditorComponent
symbol: this.symbol symbol: this.symbol
}) })
.pipe( .pipe(
catchError(({ error, message }) => { catchError(({ error, message }: HttpErrorResponse) => {
this.snackBar.open(`${error}: ${message[0]}`, undefined, { this.snackBar.open(`${error}: ${message[0]}`, undefined, {
duration: ms('3 seconds') duration: ms('3 seconds')
}); });
return EMPTY; return EMPTY;
}), }),
takeUntil(this.unsubscribeSubject) takeUntilDestroyed(this.destroyRef)
) )
.subscribe(() => { .subscribe(() => {
this.initializeHistoricalDataForm(); this.initializeHistoricalDataForm();
@ -268,11 +290,6 @@ export class GfHistoricalMarketDataEditorComponent
} }
} }
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
private initializeHistoricalDataForm() { private initializeHistoricalDataForm() {
this.historicalDataForm.setValue({ this.historicalDataForm.setValue({
historicalData: { historicalData: {

8
package-lock.json

@ -70,7 +70,7 @@
"ionicons": "8.0.13", "ionicons": "8.0.13",
"jsonpath": "1.1.1", "jsonpath": "1.1.1",
"lodash": "4.17.23", "lodash": "4.17.23",
"marked": "17.0.1", "marked": "17.0.2",
"ms": "3.0.0-canary.1", "ms": "3.0.0-canary.1",
"ng-extract-i18n-merge": "3.2.1", "ng-extract-i18n-merge": "3.2.1",
"ngx-device-detector": "11.0.0", "ngx-device-detector": "11.0.0",
@ -25625,9 +25625,9 @@
} }
}, },
"node_modules/marked": { "node_modules/marked": {
"version": "17.0.1", "version": "17.0.2",
"resolved": "https://registry.npmjs.org/marked/-/marked-17.0.1.tgz", "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.2.tgz",
"integrity": "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg==", "integrity": "sha512-s5HZGFQea7Huv5zZcAGhJLT3qLpAfnY7v7GWkICUr0+Wd5TFEtdlRR2XUL5Gg+RH7u2Df595ifrxR03mBaw7gA==",
"license": "MIT", "license": "MIT",
"bin": { "bin": {
"marked": "bin/marked.js" "marked": "bin/marked.js"

5
package.json

@ -43,10 +43,11 @@
"start:production": "npm run database:migrate && npm run database:seed && node main", "start:production": "npm run database:migrate && npm run database:seed && node main",
"start:server": "nx run api:copy-assets && nx run api:serve --watch", "start:server": "nx run api:copy-assets && nx run api:serve --watch",
"start:storybook": "nx run ui:storybook", "start:storybook": "nx run ui:storybook",
"test": "npm run test:api && npm run test:common", "test": "npx dotenv-cli -e .env.example -- npx nx run-many --target=test --all --parallel=4",
"test:api": "npx dotenv-cli -e .env.example -- nx test api", "test:api": "npx dotenv-cli -e .env.example -- nx test api",
"test:common": "npx dotenv-cli -e .env.example -- nx test common", "test:common": "npx dotenv-cli -e .env.example -- nx test common",
"test:single": "nx run api:test --test-file object.helper.spec.ts", "test:single": "nx run api:test --test-file object.helper.spec.ts",
"test:ui": "npx dotenv-cli -e .env.example -- nx test ui",
"ts-node": "ts-node", "ts-node": "ts-node",
"update": "nx migrate latest", "update": "nx migrate latest",
"watch:server": "nx run api:copy-assets && nx run api:build --watch", "watch:server": "nx run api:copy-assets && nx run api:build --watch",
@ -114,7 +115,7 @@
"ionicons": "8.0.13", "ionicons": "8.0.13",
"jsonpath": "1.1.1", "jsonpath": "1.1.1",
"lodash": "4.17.23", "lodash": "4.17.23",
"marked": "17.0.1", "marked": "17.0.2",
"ms": "3.0.0-canary.1", "ms": "3.0.0-canary.1",
"ng-extract-i18n-merge": "3.2.1", "ng-extract-i18n-merge": "3.2.1",
"ngx-device-detector": "11.0.0", "ngx-device-detector": "11.0.0",

Loading…
Cancel
Save