From ad76d21c80e80afcc06ef1cace34d2f3107892ea Mon Sep 17 00:00:00 2001 From: Jorenn92 Date: Sat, 13 Jan 2024 17:12:30 +0100 Subject: [PATCH 01/14] chore: Add backend code for rule statistics. Also add an API endpoint to test a single media item --- .../plex-api/interfaces/media.interface.ts | 13 +- .../rules/constants/constants.service.ts | 121 +++++ .../modules/rules/getter/getter.service.ts | 10 +- .../rules/getter/plex-getter.service.ts | 119 +++-- .../rules/helpers/rule.comparator.service.ts | 490 ++++++++++++++++++ .../src/modules/rules/helpers/yaml.service.ts | 140 +---- .../modules/rules/rule-executor.service.ts | 313 +---------- server/src/modules/rules/rules.controller.ts | 9 +- server/src/modules/rules/rules.module.ts | 4 + server/src/modules/rules/rules.service.ts | 29 +- ...e.comparator.service.doRuleAction.spec.ts} | 162 +++--- .../Rules/RuleGroup/AddModal/index.tsx | 2 +- 12 files changed, 834 insertions(+), 578 deletions(-) create mode 100644 server/src/modules/rules/constants/constants.service.ts create mode 100644 server/src/modules/rules/helpers/rule.comparator.service.ts rename server/src/modules/rules/tests/{rule-executor.service.doRuleAction.spec.ts => rule.comparator.service.doRuleAction.spec.ts} (74%) diff --git a/server/src/modules/api/plex-api/interfaces/media.interface.ts b/server/src/modules/api/plex-api/interfaces/media.interface.ts index e07cb95a..b259fcec 100644 --- a/server/src/modules/api/plex-api/interfaces/media.interface.ts +++ b/server/src/modules/api/plex-api/interfaces/media.interface.ts @@ -1,8 +1,10 @@ +import { PlexActor, PlexGenre } from './library.interfaces'; + export interface PlexMetadata { ratingKey: string; parentRatingKey?: string; guid: string; - type: 'movie' | 'show' | 'season'; + type: 'movie' | 'show' | 'season' | 'episode' | 'collection'; title: string; Guid: { id: string; @@ -15,13 +17,20 @@ export interface PlexMetadata { parentIndex?: number; Collection?: { tag: string }[]; leafCount: number; - grandparentRatingKey?: number; + grandparentRatingKey?: string; viewedLeafCount: number; addedAt: number; updatedAt: number; media: Media[]; parentData?: PlexMetadata; Label?: { tag: string }[]; + rating?: number; + audienceRating?: number; + userRating?: number; + Role?: PlexActor[]; + originallyAvailableAt: string; + Media: Media[]; + Genre?: PlexGenre[]; } export interface Media { diff --git a/server/src/modules/rules/constants/constants.service.ts b/server/src/modules/rules/constants/constants.service.ts new file mode 100644 index 00000000..9ce32ff2 --- /dev/null +++ b/server/src/modules/rules/constants/constants.service.ts @@ -0,0 +1,121 @@ +import { Injectable } from '@nestjs/common'; +import { RuleConstants, RuleType } from './rules.constants'; + +export interface ICustomIdentifier { + type: string; + value: string | number; +} + +@Injectable() +export class RuleConstanstService { + ruleConstants: RuleConstants; + + constructor() { + this.ruleConstants = new RuleConstants(); + } + + public getRuleConstants() { + return this.ruleConstants; + } + + public getValueIdentifier(location: [number, number]) { + const application = this.ruleConstants.applications[location[0]].name; + const rule = + this.ruleConstants.applications[location[0]].props[location[1]].name; + + return application + '.' + rule; + } + + public getValueHumanName(location: [number, number]) { + return this.ruleConstants.applications[location[0]].props[location[1]] + ?.humanName; + } + + public getValueFromIdentifier(identifier: string): [number, number] { + const application = identifier.split('.')[0]; + const rule = identifier.split('.')[1]; + + const applicationConstant = this.ruleConstants.applications.find( + (el) => el.name.toLowerCase() === application.toLowerCase(), + ); + + const ruleConstant = applicationConstant.props.find( + (el) => el.name.toLowerCase() === rule.toLowerCase(), + ); + return [applicationConstant.id, ruleConstant.id]; + } + + public getCustomValueIdentifier(customValue: { + ruleTypeId: number; + value: string; + }): ICustomIdentifier { + let ruleType: RuleType; + let value: string | number; + switch (customValue.ruleTypeId) { + case 0: + if (+customValue.value % 86400 === 0 && +customValue.value != 0) { + // when it's custom_days, translate to custom_days + ruleType = new RuleType('4', [], 'custom_days'); + value = (+customValue.value / 86400).toString(); + } else { + // otherwise, it's a normal number + ruleType = RuleType.NUMBER; + value = +customValue.value; + } + break; + case 1: + ruleType = RuleType.DATE; + value = customValue.value; + break; + case 2: + ruleType = RuleType.TEXT; + value = customValue.value; + break; + case 3: + ruleType = RuleType.BOOL; + value = customValue.value == '1' ? 'true' : 'false'; + break; + } + + return { type: ruleType.humanName, value: value }; + } + + public getCustomValueFromIdentifier(identifier: ICustomIdentifier): { + ruleTypeId: number; + value: string; + } { + let ruleType: RuleType; + let value: string; + + switch (identifier.type.toUpperCase()) { + case 'NUMBER': + ruleType = RuleType.NUMBER; + value = identifier.value.toString(); + break; + case 'DATE': + ruleType = RuleType.DATE; + value = identifier.value.toString(); + break; + case 'TEXT': + ruleType = RuleType.TEXT; + value = identifier.value.toString(); + break; + case 'BOOLEAN': + ruleType = RuleType.BOOL; + value = identifier.value == 'true' ? '1' : '0'; + break; + case 'BOOL': + ruleType = RuleType.BOOL; + value = identifier.value == 'true' ? '1' : '0'; + break; + case 'CUSTOM_DAYS': + ruleType = RuleType.NUMBER; + value = (+identifier.value * 86400).toString(); + } + + return { + ruleTypeId: +ruleType.toString(), // tostring returns the key + value: value, + }; + } +} diff --git a/server/src/modules/rules/getter/getter.service.ts b/server/src/modules/rules/getter/getter.service.ts index c3f2df7b..ec8edceb 100644 --- a/server/src/modules/rules/getter/getter.service.ts +++ b/server/src/modules/rules/getter/getter.service.ts @@ -5,7 +5,6 @@ import { OverseerrGetterService } from './overseerr-getter.service'; import { PlexGetterService } from './plex-getter.service'; import { RadarrGetterService } from './radarr-getter.service'; import { SonarrGetterService } from './sonarr-getter.service'; -import { EPlexDataType } from '../../api/plex-api/enums/plex-data-type-enum'; import { RulesDto } from '../dtos/rules.dto'; @Injectable() @@ -20,21 +19,20 @@ export class ValueGetterService { async get( [val1, val2]: [number, number], libItem: PlexLibraryItem, - dataType?: EPlexDataType, ruleGroup?: RulesDto, ) { switch (val1) { case Application.PLEX: { - return await this.plexGetter.get(val2, libItem, dataType, ruleGroup); + return await this.plexGetter.get(val2, libItem, ruleGroup); } case Application.RADARR: { - return await this.radarrGetter.get(val2, libItem, dataType); + return await this.radarrGetter.get(val2, libItem); } case Application.SONARR: { - return await this.sonarrGetter.get(val2, libItem, dataType); + return await this.sonarrGetter.get(val2, libItem); } case Application.OVERSEERR: { - return await this.overseerGetter.get(val2, libItem, dataType); + return await this.overseerGetter.get(val2, libItem); } default: { return null; diff --git a/server/src/modules/rules/getter/plex-getter.service.ts b/server/src/modules/rules/getter/plex-getter.service.ts index cc64b153..b2992e8d 100644 --- a/server/src/modules/rules/getter/plex-getter.service.ts +++ b/server/src/modules/rules/getter/plex-getter.service.ts @@ -10,7 +10,6 @@ import { Property, RuleConstants, } from '../constants/rules.constants'; -import { EPlexDataType } from 'src/modules/api/plex-api/enums/plex-data-type-enum'; import { RulesDto } from '../dtos/rules.dto'; import { PlexMetadata } from 'src/modules/api/plex-api/interfaces/media.interface'; @@ -26,26 +25,22 @@ export class PlexGetterService { ).props; } - async get( - id: number, - libItem: PlexLibraryItem, - dataType?: EPlexDataType, - ruleGroup?: RulesDto, - ) { + async get(id: number, libItem: PlexLibraryItem, ruleGroup?: RulesDto) { try { const prop = this.plexProperties.find((el) => el.id === id); + // fetch metadata from cache, this data is more complete const metadata: PlexMetadata = await this.plexApi.getMetadata( libItem.ratingKey, - ); // fetch metadata from cache, this data is more complete + ); switch (prop.name) { case 'addDate': { - return libItem.addedAt ? new Date(+libItem.addedAt * 1000) : null; + return metadata.addedAt ? new Date(+metadata.addedAt * 1000) : null; } case 'seenBy': { const plexUsers = await this.getCorrectedUsers(); const viewers: PlexSeenBy[] = await this.plexApi - .getWatchHistory(libItem.ratingKey) + .getWatchHistory(metadata.ratingKey) .catch((_err) => { return null; }); @@ -59,35 +54,35 @@ export class PlexGetterService { } } case 'releaseDate': { - return new Date(libItem.originallyAvailableAt) - ? new Date(libItem.originallyAvailableAt) + return new Date(metadata.originallyAvailableAt) + ? new Date(metadata.originallyAvailableAt) : null; } case 'rating_critics': { - return libItem.rating ? +libItem.rating : 0; + return metadata.rating ? +metadata.rating : 0; } case 'rating_audience': { - return libItem.audienceRating ? +libItem.audienceRating : 0; + return metadata.audienceRating ? +metadata.audienceRating : 0; } case 'rating_user': { - return libItem.userRating ? +libItem.userRating : 0; + return metadata.userRating ? +metadata.userRating : 0; } case 'people': { - return libItem.Role ? libItem.Role.map((el) => el.tag) : null; + return metadata.Role ? metadata.Role.map((el) => el.tag) : null; } case 'viewCount': { - const count = await this.plexApi.getWatchHistory(libItem.ratingKey); + const count = await this.plexApi.getWatchHistory(metadata.ratingKey); return count ? count.length : 0; } case 'labels': { const item = - libItem.type === 'episode' + metadata.type === 'episode' ? ((await this.plexApi.getMetadata( - libItem.grandparentRatingKey, + metadata.grandparentRatingKey, )) as unknown as PlexLibraryItem) - : libItem.type === 'season' + : metadata.type === 'season' ? ((await this.plexApi.getMetadata( - libItem.parentRatingKey, + metadata.parentRatingKey, )) as unknown as PlexLibraryItem) : metadata; @@ -109,13 +104,13 @@ export class PlexGetterService { : 0; } case 'playlists': { - if (libItem.type !== 'episode' && libItem.type !== 'movie') { + if (metadata.type !== 'episode' && metadata.type !== 'movie') { const filtered = []; const seasons = - libItem.type !== 'season' - ? await this.plexApi.getChildrenMetadata(libItem.ratingKey) - : [libItem]; + metadata.type !== 'season' + ? await this.plexApi.getChildrenMetadata(metadata.ratingKey) + : [metadata]; for (const season of seasons) { const episodes = await this.plexApi.getChildrenMetadata( season.ratingKey, @@ -136,19 +131,19 @@ export class PlexGetterService { return filtered.length; } else { const playlists = await this.plexApi.getPlaylists( - libItem.ratingKey, + metadata.ratingKey, ); return playlists.length; } } case 'playlist_names': { - if (libItem.type !== 'episode' && libItem.type !== 'movie') { + if (metadata.type !== 'episode' && metadata.type !== 'movie') { const filtered = []; const seasons = - libItem.type !== 'season' - ? await this.plexApi.getChildrenMetadata(libItem.ratingKey) - : [libItem]; + metadata.type !== 'season' + ? await this.plexApi.getChildrenMetadata(metadata.ratingKey) + : [metadata]; for (const season of seasons) { const episodes = await this.plexApi.getChildrenMetadata( season.ratingKey, @@ -169,7 +164,7 @@ export class PlexGetterService { return filtered ? filtered.map((el) => el.title.trim()) : []; } else { const playlists = await this.plexApi.getPlaylists( - libItem.ratingKey, + metadata.ratingKey, ); return playlists ? playlists.map((el) => el.title.trim()) : []; } @@ -181,7 +176,7 @@ export class PlexGetterService { } case 'lastViewedAt': { return await this.plexApi - .getWatchHistory(libItem.ratingKey) + .getWatchHistory(metadata.ratingKey) .then((seenby) => { if (seenby.length > 0) { return new Date( @@ -199,38 +194,38 @@ export class PlexGetterService { }); } case 'fileVideoResolution': { - return libItem.Media[0].videoResolution - ? libItem.Media[0].videoResolution + return metadata.Media[0].videoResolution + ? metadata.Media[0].videoResolution : null; } case 'fileBitrate': { - return libItem.Media[0].bitrate ? libItem.Media[0].bitrate : 0; + return metadata.Media[0].bitrate ? metadata.Media[0].bitrate : 0; } case 'fileVideoCodec': { - return libItem.Media[0].videoCodec - ? libItem.Media[0].videoCodec + return metadata.Media[0].videoCodec + ? metadata.Media[0].videoCodec : null; } case 'genre': { const item = - libItem.type === 'episode' + metadata.type === 'episode' ? ((await this.plexApi.getMetadata( - libItem.grandparentRatingKey, + metadata.grandparentRatingKey, )) as unknown as PlexLibraryItem) - : libItem.type === 'season' + : metadata.type === 'season' ? ((await this.plexApi.getMetadata( - libItem.parentRatingKey, + metadata.parentRatingKey, )) as unknown as PlexLibraryItem) - : libItem; + : metadata; return item.Genre ? item.Genre.map((el) => el.tag) : null; } case 'sw_allEpisodesSeenBy': { const plexUsers = await this.getCorrectedUsers(); const seasons = - libItem.type !== 'season' - ? await this.plexApi.getChildrenMetadata(libItem.ratingKey) - : [libItem]; + metadata.type !== 'season' + ? await this.plexApi.getChildrenMetadata(metadata.ratingKey) + : [metadata]; const allViewers = plexUsers.slice(); for (const season of seasons) { const episodes = await this.plexApi.getChildrenMetadata( @@ -271,7 +266,7 @@ export class PlexGetterService { const plexUsers = await this.getCorrectedUsers(); const watchHistory = await this.plexApi.getWatchHistory( - libItem.ratingKey, + metadata.ratingKey, ); const viewers = watchHistory @@ -288,7 +283,7 @@ export class PlexGetterService { } case 'sw_lastWatched': { let watchHistory = await this.plexApi.getWatchHistory( - libItem.ratingKey, + metadata.ratingKey, ); watchHistory?.sort((a, b) => a.parentIndex - b.parentIndex).reverse(); watchHistory = watchHistory?.filter( @@ -300,21 +295,21 @@ export class PlexGetterService { : null; } case 'sw_episodes': { - if (libItem.type === 'season') { + if (metadata.type === 'season') { const eps = await this.plexApi.getChildrenMetadata( - libItem.ratingKey, + metadata.ratingKey, ); return eps.length ? eps.length : 0; } - return libItem.leafCount ? +libItem.leafCount : 0; + return metadata.leafCount ? +metadata.leafCount : 0; } case 'sw_viewedEpisodes': { let viewCount = 0; const seasons = - libItem.type !== 'season' - ? await this.plexApi.getChildrenMetadata(libItem.ratingKey) - : [libItem]; + metadata.type !== 'season' + ? await this.plexApi.getChildrenMetadata(metadata.ratingKey) + : [metadata]; for (const season of seasons) { const episodes = await this.plexApi.getChildrenMetadata( season.ratingKey, @@ -332,16 +327,18 @@ export class PlexGetterService { let viewCount = 0; // for episodes - if (libItem.type === 'episode') { - const views = await this.plexApi.getWatchHistory(libItem.ratingKey); + if (metadata.type === 'episode') { + const views = await this.plexApi.getWatchHistory( + metadata.ratingKey, + ); viewCount = views?.length > 0 ? viewCount + views.length : viewCount; } else { // for seasons & shows const seasons = - libItem.type !== 'season' - ? await this.plexApi.getChildrenMetadata(libItem.ratingKey) - : [libItem]; + metadata.type !== 'season' + ? await this.plexApi.getChildrenMetadata(metadata.ratingKey) + : [metadata]; for (const season of seasons) { const episodes = await this.plexApi.getChildrenMetadata( season.ratingKey, @@ -359,11 +356,11 @@ export class PlexGetterService { } case 'sw_lastEpisodeAddedAt': { const seasons = - libItem.type !== 'season' + metadata.type !== 'season' ? ( - await this.plexApi.getChildrenMetadata(libItem.ratingKey) + await this.plexApi.getChildrenMetadata(metadata.ratingKey) ).sort((a, b) => a.index - b.index) - : [libItem]; + : [metadata]; const lastEpDate = await this.plexApi .getChildrenMetadata(seasons[seasons.length - 1].ratingKey) diff --git a/server/src/modules/rules/helpers/rule.comparator.service.ts b/server/src/modules/rules/helpers/rule.comparator.service.ts new file mode 100644 index 00000000..dcecb8f5 --- /dev/null +++ b/server/src/modules/rules/helpers/rule.comparator.service.ts @@ -0,0 +1,490 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { + RuleOperators, + RulePossibility, + RuleType, +} from '../constants/rules.constants'; +import { RuleDto } from '../dtos/rule.dto'; +import _ from 'lodash'; +import { PlexLibraryItem } from 'src/modules/api/plex-api/interfaces/library.interfaces'; +import { RulesDto } from '../dtos/rules.dto'; +import { ValueGetterService } from '../getter/getter.service'; +import { EPlexDataType } from 'src/modules/api/plex-api/enums/plex-data-type-enum'; +import { RuleDbDto } from '../dtos/ruleDb.dto'; +import { RuleConstanstService } from '../constants/constants.service'; + +interface IComparisonStatistics { + plexId: number; + sectionResults: ISectionComparisonResults[]; +} + +interface ISectionComparisonResults { + id: number; + result: boolean; + operator?: string; + ruleResults: IRuleComparisonResult[]; +} + +interface IRuleComparisonResult { + firstValueName: string; + firstValue: any; + secondValueName: string; + secondValue: any; + action: string; + operator?: string; + result: boolean; +} + +interface IComparatorReturnValue { + stats: IComparisonStatistics[]; + data: PlexLibraryItem[]; +} + +@Injectable() +export class RuleComparatorService { + private readonly logger = new Logger(RuleComparatorService.name); + workerData: PlexLibraryItem[]; + resultData: PlexLibraryItem[]; + plexData: PlexLibraryItem[]; + plexDataType: EPlexDataType; + statistics: IComparisonStatistics[]; + statisticWorker: IRuleComparisonResult[]; + enabledStats: boolean; + + constructor( + private readonly valueGetter: ValueGetterService, + private readonly ruleConstanstService: RuleConstanstService, + ) {} + + public async executeRulesWithData( + rulegroup: RulesDto, + plexData: PlexLibraryItem[], + withStats = false, + ): Promise { + try { + // prepare + this.plexData = plexData; + this.plexDataType = rulegroup.dataType ? rulegroup.dataType : undefined; + this.enabledStats = withStats; + this.workerData = []; + this.resultData = []; + this.statistics = []; + this.statisticWorker = []; + + // run rules + let currentSection = 0; + let sectionActionAnd = false; + + // prepare statistics if needed + this.prepareStatistics(); + + for (const rule of rulegroup.rules) { + const parsedRule = JSON.parse((rule as RuleDbDto).ruleJson) as RuleDto; + if (currentSection === (rule as RuleDbDto).section) { + // if section didn't change + // execute and store in work array + await this.executeRule(parsedRule, rulegroup); + } else { + // set the stat results of the completed section, if needed + this.setStatisticSectionResults(); + + // handle section action + this.handleSectionAction(sectionActionAnd); + + // save new section action + sectionActionAnd = +parsedRule.operator === 0; + // reset first operator of new section + parsedRule.operator = null; + // add new section to stats + this.addSectionToStatistics( + (rule as RuleDbDto).section, + sectionActionAnd, + ); + // Execute the rule and set the new section + await this.executeRule(parsedRule, rulegroup); + currentSection = (rule as RuleDbDto).section; + } + } + // set the stat results of the last section, if needed + this.setStatisticSectionResults(); + + // handle last section + this.handleSectionAction(sectionActionAnd); + + // return comparatorReturnValue + return { stats: this.statistics, data: this.resultData }; + } catch (e) { + this.logger.log( + `Something went wrong while running rule ${rulegroup.name}`, + ); + this.logger.debug(e); + } + } + + private setStatisticSectionResults() { + // add the result of the last section. If media is in workerData, section = true. + if (this.enabledStats) { + this.statistics.forEach((stat) => { + if (this.workerData.find((el) => +el.ratingKey === +stat.plexId)) { + stat.sectionResults[stat.sectionResults.length - 1].result = true; + } else { + stat.sectionResults[stat.sectionResults.length - 1].result = false; + } + }); + } + } + + private addSectionToStatistics(id: number, isAND: boolean) { + if (this.enabledStats) { + this.statistics.forEach((data) => { + data.sectionResults.push({ + id: id, + result: undefined, + operator: isAND ? 'AND' : 'OR', + ruleResults: [], + }); + }); + } + } + + private async executeRule(rule: RuleDto, ruleGroup: RulesDto) { + let data: PlexLibraryItem[]; + let firstVal: any; + let secondVal: any; + + if (rule.operator === null || +rule.operator === +RuleOperators.OR) { + data = _.cloneDeep(this.plexData); + } else { + data = _.cloneDeep(this.workerData); + } + + // loop media items + for (let i = data.length - 1; i >= 0; i--) { + // fetch values + firstVal = await this.valueGetter.get(rule.firstVal, data[i], ruleGroup); + secondVal = await this.getSecondValue(rule, data[i], ruleGroup, firstVal); + + if ( + (firstVal !== undefined || null) && + (secondVal !== undefined || null) + ) { + // do action + const comparisonResult = this.doRuleAction( + firstVal, + secondVal, + rule.action, + ); + + // add stats if enabled + this.addStatistictoParent( + rule, + firstVal, + secondVal, + +data[i].ratingKey, + comparisonResult, + ); + + // alter workerData + if (rule.operator === null || +rule.operator === +RuleOperators.OR) { + if (comparisonResult) { + // add to workerdata if not yet available + if ( + this.workerData.find((e) => e.ratingKey === data[i].ratingKey) === + undefined + ) { + this.workerData.push(data[i]); + } + } + } else { + if (!comparisonResult) { + // remove from workerdata + this.workerData.splice(i, 1); + } + } + } + } + } + + async getSecondValue( + rule: RuleDto, + data: PlexLibraryItem, + rulegroup: RulesDto, + firstVal: any, + ): Promise { + let secondVal; + if (rule.lastVal) { + secondVal = await this.valueGetter.get(rule.lastVal, data, rulegroup); + } else { + secondVal = + rule.customVal.ruleTypeId === +RuleType.DATE + ? rule.customVal.value.includes('-') + ? new Date(rule.customVal.value) + : new Date(+rule.customVal.value * 1000) + : rule.customVal.ruleTypeId === +RuleType.TEXT + ? rule.customVal.value + : rule.customVal.ruleTypeId === +RuleType.NUMBER || + rule.customVal.ruleTypeId === +RuleType.BOOL + ? +rule.customVal.value + : null; + if ( + firstVal instanceof Date && + rule.customVal.ruleTypeId === +RuleType.NUMBER + ) { + if ( + [RulePossibility.IN_LAST, RulePossibility.BEFORE].includes( + rule.action, + ) + ) { + secondVal = new Date(new Date().getTime() - +secondVal * 1000); + } else { + secondVal = new Date(new Date().getTime() + +secondVal * 1000); + } + } else if ( + firstVal instanceof Date && + rule.customVal.ruleTypeId === +RuleType.DATE + ) { + secondVal = new Date(+secondVal); + } + if ( + // if custom secondval is text, check if it's parsable as an array + rule.customVal.ruleTypeId === +RuleType.TEXT && + this.isStringParsableToArray(secondVal as string) + ) { + secondVal = JSON.parse(secondVal); + } + } + return secondVal; + } + + private prepareStatistics() { + if (this.enabledStats) { + this.plexData.forEach((data) => { + this.statistics.push({ + plexId: +data.ratingKey, + sectionResults: [ + { + id: 0, + result: undefined, + ruleResults: [], + }, + ], + }); + }); + } + } + + private addStatistictoParent( + rule: RuleDto, + firstVal: any, + secondVal: any, + plexId: number, + result: boolean, + ) { + if (this.enabledStats) { + const index = this.statistics.findIndex((el) => +el.plexId === +plexId); + const sectionIndex = this.statistics[index].sectionResults.length - 1; + + // push result to currently last section + this.statistics[index].sectionResults[sectionIndex].ruleResults.push({ + operator: + rule.operator === null || rule.operator === undefined + ? RuleOperators[1] + : RuleOperators[rule.operator], + action: RulePossibility[rule.action].toLowerCase(), + firstValueName: this.ruleConstanstService.getValueHumanName( + rule.firstVal, + ), + firstValue: firstVal, + secondValueName: rule.lastVal + ? this.ruleConstanstService.getValueHumanName(rule.lastVal) + : this.ruleConstanstService.getCustomValueIdentifier(rule.customVal) + .type, + secondValue: secondVal, + result: result, + }); + + // If it's the first rule of a section (but not the first one) then add the operator to the sectionResult + if ( + index > 0 && + this.statistics[index].sectionResults[sectionIndex].ruleResults + .length === 1 + ) { + this.statistics[index].sectionResults[sectionIndex].operator = + rule.operator === null || rule.operator === undefined + ? RuleOperators[1] + : RuleOperators[rule.operator]; + } + } + } + + private handleSectionAction(sectionActionAnd: boolean) { + if (!sectionActionAnd) { + // section action is OR, then push in result array + this.resultData.push(...this.workerData); + } else { + // section action is AND, then filter media not in work array out of result array + this.resultData = this.resultData.filter((el) => { + // If in current data.. Otherwise we're removing previously added media + if (this.plexData.some((plexEl) => plexEl.ratingKey === el.ratingKey)) { + return this.workerData.some( + (workEl) => workEl.ratingKey === el.ratingKey, + ); + } else { + // If not in current data, skip check + return true; + } + }); + } + // empty workerdata. prepare for execution of new section + this.workerData = []; + } + + private doRuleAction(val1: T, val2: T, action: RulePossibility): boolean { + if ( + typeof val1 === 'string' || + (Array.isArray(val1) ? typeof val1[0] === 'string' : false) + ) { + val1 = Array.isArray(val1) + ? (val1.map((el) => el?.toLowerCase()) as unknown as T) + : ((val1 as string)?.toLowerCase() as unknown as T); + } + if ( + typeof val2 === 'string' || + (Array.isArray(val2) ? typeof val2[0] === 'string' : false) + ) { + val2 = Array.isArray(val2) + ? (val2.map((el) => el?.toLowerCase()) as unknown as T) + : ((val2 as string)?.toLowerCase() as unknown as T); + } + if (action === RulePossibility.BIGGER) { + return val1 > val2; + } + if (action === RulePossibility.SMALLER) { + return val1 < val2; + } + if (action === RulePossibility.EQUALS) { + if (!Array.isArray(val1)) { + if (val1 instanceof Date && val2 instanceof Date) { + return ( + new Date(val1?.toDateString()).valueOf() === + new Date(val2?.toDateString()).valueOf() + ); + } + if (typeof val1 === 'boolean') { + return val1 == val2; + } + return val1 === val2; + } else { + if (val1.length > 0) { + return val1?.every((e) => { + e = + typeof e === 'string' + ? (e as unknown as string)?.toLowerCase() + : e; + if (Array.isArray(val2)) { + return (val2 as unknown as T[])?.includes(e); + } else { + return e === val2; + } + }); + } else { + return false; + } + } + } + if (action === RulePossibility.NOT_EQUALS) { + return !this.doRuleAction(val1, val2, RulePossibility.EQUALS); + } + if (action === RulePossibility.CONTAINS) { + try { + if (!Array.isArray(val2)) { + return (val1 as unknown as T[])?.includes(val2); + } else { + if (val2.length > 0) { + return val2?.some((el) => { + return (val1 as unknown as T[])?.includes(el); + }); + } else { + return false; + } + } + } catch (_err) { + return null; + } + } + if (action === RulePossibility.CONTAINS_PARTIAL) { + try { + if (!Array.isArray(val2)) { + // return (val1 as unknown as T[])?.includes(val2); + return ( + (Array.isArray(val1) ? (val1 as unknown as T[]) : [val1])?.some( + (line) => { + return typeof line === 'string' && + val2 != undefined && + String(val2).length > 0 + ? line.includes(String(val2)) + : line == val2 + ? true + : false; + }, + ) || false + ); + } else { + if (val2.length > 0) { + return val2?.some((el) => { + // return (val1 as unknown as T[])?.includes(el); + return ( + (val1 as unknown as T[])?.some((line) => { + return typeof line === 'string' && + el != undefined && + el.length > 0 + ? line.includes(String(el)) + : line == el + ? true + : false; + }) || false + ); + }); + } else { + return false; + } + } + } catch (_err) { + return null; + } + } + if (action === RulePossibility.NOT_CONTAINS) { + return !this.doRuleAction(val1, val2, RulePossibility.CONTAINS); + } + if (action === RulePossibility.NOT_CONTAINS_PARTIAL) { + return !this.doRuleAction(val1, val2, RulePossibility.CONTAINS_PARTIAL); + } + if (action === RulePossibility.BEFORE) { + return val1 && val2 ? val1 <= val2 : false; + } + if (action === RulePossibility.AFTER) { + return val1 && val2 ? val1 >= val2 : false; + } + if (action === RulePossibility.IN_LAST) { + return ( + val1 >= val2 && // time in s + (val1 as unknown as Date) <= new Date() + ); + } + if (action === RulePossibility.IN_NEXT) { + return ( + val1 <= val2 && // time in s + (val1 as unknown as Date) >= new Date() + ); + } + } + + private isStringParsableToArray(str: string) { + try { + const array = JSON.parse(str); + return Array.isArray(array); + } catch (error) { + return false; + } + } +} diff --git a/server/src/modules/rules/helpers/yaml.service.ts b/server/src/modules/rules/helpers/yaml.service.ts index 838ee4c0..f106efc1 100644 --- a/server/src/modules/rules/helpers/yaml.service.ts +++ b/server/src/modules/rules/helpers/yaml.service.ts @@ -2,16 +2,18 @@ import { Injectable, Logger } from '@nestjs/common'; import { RuleDto } from '../dtos/rule.dto'; import { ReturnStatus } from '../rules.service'; import { - RuleConstants, RuleOperators, RulePossibility, - RuleType, } from '../constants/rules.constants'; import YAML from 'yaml'; import { EPlexDataType, PlexDataTypeStrings, } from '../../..//modules/api/plex-api/enums/plex-data-type-enum'; +import { + ICustomIdentifier, + RuleConstanstService, +} from '../constants/constants.service'; interface IRuleYamlParent { mediaType: string; @@ -22,27 +24,19 @@ interface ISectionYaml { [key: number]: IRuleYaml[]; } -interface ICustomYamlValue { - type: string; - value: string | number; -} - interface IRuleYaml { operator?: string; action: string; firstValue: string; lastValue?: string; - customValue?: ICustomYamlValue; + customValue?: ICustomIdentifier; } @Injectable() export class RuleYamlService { private readonly logger = new Logger(RuleYamlService.name); - ruleConstants: RuleConstants; - constructor() { - this.ruleConstants = new RuleConstants(); - } + constructor(private readonly ruleConstanstService: RuleConstanstService) {} public encode(rules: RuleDto[], mediaType: number): ReturnStatus { try { let workingSection = { id: 0, rules: [] }; @@ -60,13 +54,23 @@ export class RuleYamlService { // transform rule and add to workingSection workingSection.rules.push({ ...(rule.operator ? { operator: RuleOperators[+rule.operator] } : {}), - firstValue: this.getValueIdentifier(rule.firstVal), + firstValue: this.ruleConstanstService.getValueIdentifier( + rule.firstVal, + ), action: RulePossibility[+rule.action], ...(rule.lastVal - ? { lastValue: this.getValueIdentifier(rule.lastVal) } + ? { + lastValue: this.ruleConstanstService.getValueIdentifier( + rule.lastVal, + ), + } : {}), ...(rule.customVal - ? { customValue: this.getCustomValueIdentifier(rule.customVal) } + ? { + customValue: this.ruleConstanstService.getCustomValueIdentifier( + rule.customVal, + ), + } : {}), }); } @@ -124,21 +128,22 @@ export class RuleYamlService { : null, action: +RulePossibility[rule.action.toUpperCase()], section: idRef, - firstVal: this.getValueFromIdentifier( + firstVal: this.ruleConstanstService.getValueFromIdentifier( rule.firstValue.toLowerCase(), ), ...(rule.lastValue ? { - lastVal: this.getValueFromIdentifier( + lastVal: this.ruleConstanstService.getValueFromIdentifier( rule.lastValue.toLowerCase(), ), } : {}), ...(rule.customValue ? { - customVal: this.getCustomValueFromIdentifier( - rule.customValue, - ), + customVal: + this.ruleConstanstService.getCustomValueFromIdentifier( + rule.customValue, + ), } : {}), }); @@ -165,99 +170,4 @@ export class RuleYamlService { }; } } - private getValueIdentifier(location: [number, number]) { - const application = this.ruleConstants.applications[location[0]].name; - const rule = - this.ruleConstants.applications[location[0]].props[location[1]].name; - - return application + '.' + rule; - } - - private getValueFromIdentifier(identifier: string): [number, number] { - const application = identifier.split('.')[0]; - const rule = identifier.split('.')[1]; - - const applicationConstant = this.ruleConstants.applications.find( - (el) => el.name.toLowerCase() === application.toLowerCase(), - ); - - const ruleConstant = applicationConstant.props.find( - (el) => el.name.toLowerCase() === rule.toLowerCase(), - ); - return [applicationConstant.id, ruleConstant.id]; - } - - private getCustomValueIdentifier(customValue: { - ruleTypeId: number; - value: string; - }): ICustomYamlValue { - let ruleType: RuleType; - let value: string | number; - switch (customValue.ruleTypeId) { - case 0: - if (+customValue.value % 86400 === 0 && +customValue.value != 0) { - // when it's custom_days, translate to custom_days - ruleType = new RuleType('4', [], 'custom_days'); - value = (+customValue.value / 86400).toString(); - } else { - // otherwise, it's a normal number - ruleType = RuleType.NUMBER; - value = +customValue.value; - } - break; - case 1: - ruleType = RuleType.DATE; - value = customValue.value; - break; - case 2: - ruleType = RuleType.TEXT; - value = customValue.value; - break; - case 3: - ruleType = RuleType.BOOL; - value = customValue.value == '1' ? 'true' : 'false'; - break; - } - - return { type: ruleType.humanName, value: value }; - } - - private getCustomValueFromIdentifier(identifier: ICustomYamlValue): { - ruleTypeId: number; - value: string; - } { - let ruleType: RuleType; - let value: string; - - switch (identifier.type.toUpperCase()) { - case 'NUMBER': - ruleType = RuleType.NUMBER; - value = identifier.value.toString(); - break; - case 'DATE': - ruleType = RuleType.DATE; - value = identifier.value.toString(); - break; - case 'TEXT': - ruleType = RuleType.TEXT; - value = identifier.value.toString(); - break; - case 'BOOLEAN': - ruleType = RuleType.BOOL; - value = identifier.value == 'true' ? '1' : '0'; - break; - case 'BOOL': - ruleType = RuleType.BOOL; - value = identifier.value == 'true' ? '1' : '0'; - break; - case 'CUSTOM_DAYS': - ruleType = RuleType.NUMBER; - value = (+identifier.value * 86400).toString(); - } - - return { - ruleTypeId: +ruleType.toString(), // tostring returns the key - value: value, - }; - } } diff --git a/server/src/modules/rules/rule-executor.service.ts b/server/src/modules/rules/rule-executor.service.ts index b09b993d..0672221d 100644 --- a/server/src/modules/rules/rule-executor.service.ts +++ b/server/src/modules/rules/rule-executor.service.ts @@ -1,26 +1,19 @@ import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common'; import _ from 'lodash'; -import { isNull } from 'lodash'; import { PlexLibraryItem } from '../api/plex-api/interfaces/library.interfaces'; import { PlexApiService } from '../api/plex-api/plex-api.service'; import { CollectionsService } from '../collections/collections.service'; import { AddCollectionMedia } from '../collections/interfaces/collection-media.interface'; import { SettingsService } from '../settings/settings.service'; import { TasksService } from '../tasks/tasks.service'; -import { - RuleConstants, - RuleOperators, - RulePossibility, - RuleType, -} from './constants/rules.constants'; -import { RuleDto } from './dtos/rule.dto'; -import { RuleDbDto } from './dtos/ruleDb.dto'; +import { RuleConstants } from './constants/rules.constants'; + import { RulesDto } from './dtos/rules.dto'; import { RuleGroup } from './entities/rule-group.entities'; -import { ValueGetterService } from './getter/getter.service'; import { RulesService } from './rules.service'; import { EPlexDataType } from '../api/plex-api/enums/plex-data-type-enum'; import cacheManager, { Cache } from '../api/lib/cache'; +import { RuleComparatorService } from './helpers/rule.comparator.service'; interface PlexData { page: number; @@ -41,11 +34,11 @@ export class RuleExecutorService implements OnApplicationBootstrap { resultData: PlexLibraryItem[]; constructor( private readonly rulesService: RulesService, - private readonly valueGetter: ValueGetterService, private readonly plexApi: PlexApiService, private readonly collectionService: CollectionsService, private readonly taskService: TasksService, private readonly settings: SettingsService, + private readonly comparator: RuleComparatorService, ) { this.ruleConstants = new RuleConstants(); this.plexData = { page: 1, finished: false, data: [] }; @@ -96,39 +89,21 @@ export class RuleExecutorService implements OnApplicationBootstrap { if (rulegroup.useRules) { this.logger.log(`Executing rules for '${rulegroup.name}'`); + // prepare this.workerData = []; this.resultData = []; - this.plexData = { page: 0, finished: false, data: [] }; - this.plexDataType = rulegroup.dataType - ? rulegroup.dataType - : undefined; + + // Run rules data shunks of 50 while (!this.plexData.finished) { await this.getPlexData(rulegroup.libraryId); - let currentSection = 0; - let sectionActionAnd = false; - - for (const rule of rulegroup.rules) { - const parsedRule = JSON.parse( - (rule as RuleDbDto).ruleJson, - ) as RuleDto; - if (currentSection === (rule as RuleDbDto).section) { - // if section didn't change - // execute and store in work array - await this.executeRule(parsedRule, rulegroup); - } else { - // handle section action - this.handleSectionAction(sectionActionAnd); - // save new section action - sectionActionAnd = +parsedRule.operator === 0; - // reset first operator of new section - parsedRule.operator = null; - // Execute the rule and set the new section - await this.executeRule(parsedRule, rulegroup); - currentSection = (rule as RuleDbDto).section; - } + const ruleResult = await this.comparator.executeRulesWithData( + rulegroup, + this.plexData.data, + ); + if (ruleResult) { + this.resultData.push(...ruleResult?.data); } - this.handleSectionAction(sectionActionAnd); // Handle last section } await this.handleCollection( await this.rulesService.getRuleGroupById(rulegroup.id), // refetch to get latest changes @@ -211,30 +186,6 @@ export class RuleExecutorService implements OnApplicationBootstrap { } } - private handleSectionAction(sectionActionAnd: boolean) { - if (!sectionActionAnd) { - // section action is OR, then push in result array - this.resultData.push(...this.workerData); - } else { - // section action is AND, then filter media not in work array out of result array - this.resultData = this.resultData.filter((el) => { - // If in current data.. Otherwise we're removing previously added media - if ( - this.plexData.data.some((plexEl) => plexEl.ratingKey === el.ratingKey) - ) { - return this.workerData.some( - (workEl) => workEl.ratingKey === el.ratingKey, - ); - } else { - // If not in current data, skip check - return true; - } - }); - } - // empty workerdata. prepare for execution of new section - this.workerData = []; - } - private async handleCollection(rulegroup: RuleGroup) { try { let collection = await this.collectionService.getCollection( @@ -386,245 +337,7 @@ export class RuleExecutorService implements OnApplicationBootstrap { this.plexData.page++; } - private async executeRule(rule: RuleDto, ruleGroup: RulesDto) { - let data: PlexLibraryItem[]; - let firstVal: any; - let secondVal: any; - const indexesToSplice: number[] = []; - - if (isNull(rule.operator) || +rule.operator === +RuleOperators.OR) { - data = _.cloneDeep(this.plexData.data); - } else { - data = _.cloneDeep(this.workerData); - } - // for (const [index, el] of data.entries()) { - for (let i = data.length - 1; i >= 0; i--) { - firstVal = await this.valueGetter.get( - rule.firstVal, - data[i], - this.plexDataType, - ruleGroup, - ); - if (rule.lastVal) { - secondVal = await this.valueGetter.get( - rule.lastVal, - data[i], - this.plexDataType, - ruleGroup, - ); - } else { - secondVal = - rule.customVal.ruleTypeId === +RuleType.DATE - ? rule.customVal.value.includes('-') - ? new Date(rule.customVal.value) - : new Date(+rule.customVal.value * 1000) - : rule.customVal.ruleTypeId === +RuleType.TEXT - ? rule.customVal.value - : rule.customVal.ruleTypeId === +RuleType.NUMBER || - rule.customVal.ruleTypeId === +RuleType.BOOL - ? +rule.customVal.value - : null; - if ( - firstVal instanceof Date && - rule.customVal.ruleTypeId === +RuleType.NUMBER - ) { - if ( - [RulePossibility.IN_LAST, RulePossibility.BEFORE].includes( - rule.action, - ) - ) { - secondVal = new Date(new Date().getTime() - +secondVal * 1000); - } else { - secondVal = new Date(new Date().getTime() + +secondVal * 1000); - } - } else if ( - firstVal instanceof Date && - rule.customVal.ruleTypeId === +RuleType.DATE - ) { - secondVal = new Date(+secondVal); - } - if ( - // if custom secondval is text, check if it's parsable as an array - rule.customVal.ruleTypeId === +RuleType.TEXT && - this.isStringParsableToArray(secondVal as string) - ) { - secondVal = JSON.parse(secondVal); - } - } - if ( - (firstVal !== undefined || null) && - (secondVal !== undefined || null) - ) { - if (isNull(rule.operator) || +rule.operator === +RuleOperators.OR) { - if (this.doRuleAction(firstVal, secondVal, rule.action)) { - // add to workerdata if not yet available - if ( - this.workerData.find((e) => e.ratingKey === data[i].ratingKey) === - undefined - ) { - this.workerData.push(data[i]); - } - } - } else { - if (!this.doRuleAction(firstVal, secondVal, rule.action)) { - // remove from workerdata - this.workerData.splice(i, 1); - } - } - } - } - } - - private doRuleAction(val1: T, val2: T, action: RulePossibility): boolean { - if ( - typeof val1 === 'string' || - (Array.isArray(val1) ? typeof val1[0] === 'string' : false) - ) { - val1 = Array.isArray(val1) - ? (val1.map((el) => el?.toLowerCase()) as unknown as T) - : ((val1 as string)?.toLowerCase() as unknown as T); - } - if ( - typeof val2 === 'string' || - (Array.isArray(val2) ? typeof val2[0] === 'string' : false) - ) { - val2 = Array.isArray(val2) - ? (val2.map((el) => el?.toLowerCase()) as unknown as T) - : ((val2 as string)?.toLowerCase() as unknown as T); - } - if (action === RulePossibility.BIGGER) { - return val1 > val2; - } - if (action === RulePossibility.SMALLER) { - return val1 < val2; - } - if (action === RulePossibility.EQUALS) { - if (!Array.isArray(val1)) { - if (val1 instanceof Date && val2 instanceof Date) { - return ( - new Date(val1?.toDateString()).valueOf() === - new Date(val2?.toDateString()).valueOf() - ); - } - if (typeof val1 === 'boolean') { - return val1 == val2; - } - return val1 === val2; - } else { - if (val1.length > 0) { - return val1?.every((e) => { - e = - typeof e === 'string' - ? (e as unknown as string)?.toLowerCase() - : e; - if (Array.isArray(val2)) { - return (val2 as unknown as T[])?.includes(e); - } else { - return e === val2; - } - }); - } else { - return false; - } - } - } - if (action === RulePossibility.NOT_EQUALS) { - return !this.doRuleAction(val1, val2, RulePossibility.EQUALS); - } - if (action === RulePossibility.CONTAINS) { - try { - if (!Array.isArray(val2)) { - return (val1 as unknown as T[])?.includes(val2); - } else { - if (val2.length > 0) { - return val2?.some((el) => { - return (val1 as unknown as T[])?.includes(el); - }); - } else { - return false; - } - } - } catch (_err) { - return null; - } - } - if (action === RulePossibility.CONTAINS_PARTIAL) { - try { - if (!Array.isArray(val2)) { - // return (val1 as unknown as T[])?.includes(val2); - return ( - (Array.isArray(val1) ? (val1 as unknown as T[]) : [val1])?.some( - (line) => { - return typeof line === 'string' && - val2 != undefined && - String(val2).length > 0 - ? line.includes(String(val2)) - : line == val2 - ? true - : false; - }, - ) || false - ); - } else { - if (val2.length > 0) { - return val2?.some((el) => { - // return (val1 as unknown as T[])?.includes(el); - return ( - (val1 as unknown as T[])?.some((line) => { - return typeof line === 'string' && - el != undefined && - el.length > 0 - ? line.includes(String(el)) - : line == el - ? true - : false; - }) || false - ); - }); - } else { - return false; - } - } - } catch (_err) { - return null; - } - } - if (action === RulePossibility.NOT_CONTAINS) { - return !this.doRuleAction(val1, val2, RulePossibility.CONTAINS); - } - if (action === RulePossibility.NOT_CONTAINS_PARTIAL) { - return !this.doRuleAction(val1, val2, RulePossibility.CONTAINS_PARTIAL); - } - if (action === RulePossibility.BEFORE) { - return val1 && val2 ? val1 <= val2 : false; - } - if (action === RulePossibility.AFTER) { - return val1 && val2 ? val1 >= val2 : false; - } - if (action === RulePossibility.IN_LAST) { - return ( - val1 >= val2 && // time in s - (val1 as unknown as Date) <= new Date() - ); - } - if (action === RulePossibility.IN_NEXT) { - return ( - val1 <= val2 && // time in s - (val1 as unknown as Date) >= new Date() - ); - } - } - private async logInfo(message: string) { this.logger.log(message); } - - private isStringParsableToArray(str: string) { - try { - const array = JSON.parse(str); - return Array.isArray(array); - } catch (error) { - return false; - } - } } diff --git a/server/src/modules/rules/rules.controller.ts b/server/src/modules/rules/rules.controller.ts index 909c932d..1b1ae1f5 100644 --- a/server/src/modules/rules/rules.controller.ts +++ b/server/src/modules/rules/rules.controller.ts @@ -13,7 +13,6 @@ import { ExclusionAction, ExclusionContextDto } from './dtos/exclusion.dto'; import { RulesDto } from './dtos/rules.dto'; import { RuleExecutorService } from './rule-executor.service'; import { ReturnStatus, RulesService } from './rules.service'; -import { RuleDto } from './dtos/rule.dto'; @Controller('api/rules') export class RulesController { @@ -176,4 +175,12 @@ export class RulesController { }; } } + + @Post('/test') + async testRuleGroup(@Body() body: { mediaId: string; rulegroupId: number }) { + return this.rulesService.testRuleGroupWithData( + body.rulegroupId, + body.mediaId, + ); + } } diff --git a/server/src/modules/rules/rules.module.ts b/server/src/modules/rules/rules.module.ts index 1bb0624d..82ca50aa 100644 --- a/server/src/modules/rules/rules.module.ts +++ b/server/src/modules/rules/rules.module.ts @@ -23,6 +23,8 @@ import { CommunityRuleKarma } from './entities/community-rule-karma.entities'; import { Settings } from '../settings/entities/settings.entities'; import { RuleMaintenanceService } from './rule-maintenance.service'; import { RuleYamlService } from './helpers/yaml.service'; +import { RuleComparatorService } from './helpers/rule.comparator.service'; +import { RuleConstanstService } from './constants/constants.service'; @Module({ imports: [ @@ -52,6 +54,8 @@ import { RuleYamlService } from './helpers/yaml.service'; OverseerrGetterService, ValueGetterService, RuleYamlService, + RuleComparatorService, + RuleConstanstService ], controllers: [RulesController], }) diff --git a/server/src/modules/rules/rules.service.ts b/server/src/modules/rules/rules.service.ts index fabdf2b7..3f0aea12 100644 --- a/server/src/modules/rules/rules.service.ts +++ b/server/src/modules/rules/rules.service.ts @@ -26,6 +26,8 @@ import { Settings } from '../settings/entities/settings.entities'; import _ from 'lodash'; import { AddCollectionMedia } from '../collections/interfaces/collection-media.interface'; import { RuleYamlService } from './helpers/yaml.service'; +import { RuleComparatorService } from './helpers/rule.comparator.service'; +import { PlexLibraryItem } from '../api/plex-api/interfaces/library.interfaces'; export interface ReturnStatus { code: 0 | 1; @@ -59,6 +61,7 @@ export class RulesService { private readonly plexApi: PlexApiService, private readonly connection: Connection, private readonly ruleYamlService: RuleYamlService, + private readonly RuleComparatorService: RuleComparatorService, ) { this.ruleConstants = new RuleConstants(); } @@ -121,8 +124,8 @@ export class RulesService { libraryId !== undefined ? `rg.libraryId = ${libraryId}` : typeId !== undefined - ? `c.type = ${typeId}` - : 'rg.libraryId != -1', + ? `c.type = ${typeId}` + : 'rg.libraryId != -1', ) // .where(typeId !== undefined ? `c.type = ${typeId}` : '') .getMany(); @@ -191,8 +194,8 @@ export class RulesService { lib.type === 'movie' ? EPlexDataType.MOVIES : params.dataType !== undefined - ? params.dataType - : EPlexDataType.SHOWS, + ? params.dataType + : EPlexDataType.SHOWS, title: params.name, description: params.description, arrAction: params.arrAction ? params.arrAction : 0, @@ -709,4 +712,22 @@ export class RulesService { public decodeFromYaml(yaml: string, mediaType: number): ReturnStatus { return this.ruleYamlService.decode(yaml, mediaType); } + + public async testRuleGroupWithData( + rulegroupId: number, + mediaId: string, + ): Promise { + const mediaResp = await this.plexApi.getMetadata(mediaId); + const group = await this.getRuleGroupById(rulegroupId); + if (group && mediaResp) { + group.rules = await this.getRules(group.id.toString()); + const result = await this.RuleComparatorService.executeRulesWithData( + group as RulesDto, + [mediaResp as unknown as PlexLibraryItem], + true, + ); + return { code: 1, result: result.stats }; + } + return { code: 0, result: 'Invalid input' }; + } } diff --git a/server/src/modules/rules/tests/rule-executor.service.doRuleAction.spec.ts b/server/src/modules/rules/tests/rule.comparator.service.doRuleAction.spec.ts similarity index 74% rename from server/src/modules/rules/tests/rule-executor.service.doRuleAction.spec.ts rename to server/src/modules/rules/tests/rule.comparator.service.doRuleAction.spec.ts index 5914d188..e8307717 100644 --- a/server/src/modules/rules/tests/rule-executor.service.doRuleAction.spec.ts +++ b/server/src/modules/rules/tests/rule.comparator.service.doRuleAction.spec.ts @@ -1,33 +1,19 @@ import { TestBed } from '@automock/jest'; import { RulePossibility } from '../constants/rules.constants'; -import { RuleExecutorService } from '../rule-executor.service'; -import { PlexApiService } from '../../api/plex-api/plex-api.service'; -import { CollectionsService } from '../../collections/collections.service'; -import { SettingsService } from '../../settings/settings.service'; -import { TasksService } from '../../tasks/tasks.service'; import { ValueGetterService } from '../getter/getter.service'; -import { RulesService } from '../rules.service'; +import { RuleComparatorService } from '../helpers/rule.comparator.service'; -describe('RuleExecutorService', () => { - let ruleExecutorService: RuleExecutorService; +describe('RuleComparatorService', () => { + let ruleComparatorService: RuleComparatorService; beforeEach(async () => { - const { unit } = TestBed.create(RuleExecutorService) - .mock(RulesService) - .using({ getRuleGroups: jest.fn().mockResolvedValue([]) }) + const { unit } = TestBed.create(RuleComparatorService) + .mock(ValueGetterService) .using({ get: jest.fn() }) - .mock(PlexApiService) - .using({}) - .mock(CollectionsService) - .using({}) - .mock(TasksService) - .using({}) - .mock(SettingsService) - .using({}) .compile(); - ruleExecutorService = unit; + ruleComparatorService = unit; }); describe('doRuleAction', () => { @@ -35,7 +21,7 @@ describe('RuleExecutorService', () => { const val1 = 'abc'; const val2 = 'abc'; const action = RulePossibility.EQUALS; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(true); }); @@ -43,7 +29,7 @@ describe('RuleExecutorService', () => { const val1 = 'abc'; const val2 = ''; const action = RulePossibility.EQUALS; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(false); }); @@ -51,7 +37,7 @@ describe('RuleExecutorService', () => { const val1 = 'abc'; const val2 = undefined; const action = RulePossibility.EQUALS; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(false); }); @@ -59,7 +45,7 @@ describe('RuleExecutorService', () => { const val1 = 'abc'; const val2 = 'abd'; const action = RulePossibility.EQUALS; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(false); }); @@ -67,7 +53,7 @@ describe('RuleExecutorService', () => { const val1 = 'abc'; const val2 = 'abc'; const action = RulePossibility.NOT_EQUALS; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(false); }); @@ -75,7 +61,7 @@ describe('RuleExecutorService', () => { const val1 = 'abc'; const val2 = 'abd'; const action = RulePossibility.NOT_EQUALS; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(true); }); @@ -83,7 +69,7 @@ describe('RuleExecutorService', () => { const val1 = ['abc', 'def']; const val2 = ['abc', 'def']; const action = RulePossibility.EQUALS; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(true); }); @@ -91,7 +77,7 @@ describe('RuleExecutorService', () => { const val1 = ['abc', 'def']; const val2 = ['abc', 'cde']; const action = RulePossibility.EQUALS; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(false); }); @@ -99,7 +85,7 @@ describe('RuleExecutorService', () => { const val1 = ['abc', 'def']; const val2 = ['abc', 'def']; const action = RulePossibility.NOT_EQUALS; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(false); }); @@ -107,7 +93,7 @@ describe('RuleExecutorService', () => { const val1 = ['abc', 'def']; const val2 = ['abc', 'cde']; const action = RulePossibility.NOT_EQUALS; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(true); }); @@ -115,7 +101,7 @@ describe('RuleExecutorService', () => { const val1 = new Date('2022-01-01'); const val2 = new Date('2022-01-01'); const action = RulePossibility.EQUALS; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(true); }); @@ -123,7 +109,7 @@ describe('RuleExecutorService', () => { const val1 = new Date('2022-01-01'); const val2 = new Date('2022-01-02'); const action = RulePossibility.EQUALS; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(false); }); @@ -131,7 +117,7 @@ describe('RuleExecutorService', () => { const val1 = new Date('2022-01-01'); const val2 = new Date('2022-01-01'); const action = RulePossibility.NOT_EQUALS; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(false); }); @@ -139,7 +125,7 @@ describe('RuleExecutorService', () => { const val1 = new Date('2022-01-01'); const val2 = new Date('2022-01-02'); const action = RulePossibility.NOT_EQUALS; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(true); }); @@ -147,7 +133,7 @@ describe('RuleExecutorService', () => { const val1 = 5; const val2 = 5; const action = RulePossibility.EQUALS; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(true); }); @@ -155,7 +141,7 @@ describe('RuleExecutorService', () => { const val1 = 5; const val2 = 4; const action = RulePossibility.EQUALS; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(false); }); @@ -163,7 +149,7 @@ describe('RuleExecutorService', () => { const val1 = 5; const val2 = 5; const action = RulePossibility.NOT_EQUALS; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(false); }); @@ -171,7 +157,7 @@ describe('RuleExecutorService', () => { const val1 = 5; const val2 = 4; const action = RulePossibility.NOT_EQUALS; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(true); }); @@ -179,7 +165,7 @@ describe('RuleExecutorService', () => { const val1 = 'abc'; const val2 = 'ab'; const action = RulePossibility.CONTAINS; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(true); }); @@ -187,7 +173,7 @@ describe('RuleExecutorService', () => { const val1 = 'abc'; const val2 = 'de'; const action = RulePossibility.CONTAINS; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(false); }); @@ -195,7 +181,7 @@ describe('RuleExecutorService', () => { const val1 = 'abc'; const val2 = 'de'; const action = RulePossibility.CONTAINS; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(false); }); @@ -203,7 +189,7 @@ describe('RuleExecutorService', () => { const val1 = 'abc'; const val2 = 'ab'; const action = RulePossibility.CONTAINS; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(true); }); @@ -211,7 +197,7 @@ describe('RuleExecutorService', () => { const val1 = ['abc', 'def']; const val2 = ['abc']; const action = RulePossibility.CONTAINS; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(true); }); @@ -219,7 +205,7 @@ describe('RuleExecutorService', () => { const val1 = ['abc', 'def']; const val2 = ['abc']; const action = RulePossibility.CONTAINS_PARTIAL; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(true); }); @@ -227,7 +213,7 @@ describe('RuleExecutorService', () => { const val1 = ['ImDb top 250', 'My birthday', 'jef']; const val2 = ['imdb']; const action = RulePossibility.CONTAINS_PARTIAL; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(true); }); @@ -235,7 +221,7 @@ describe('RuleExecutorService', () => { const val1 = ['ImDb top 250', 'My birthday', 'jef']; const val2 = ['imdb']; const action = RulePossibility.CONTAINS; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(false); }); @@ -243,7 +229,7 @@ describe('RuleExecutorService', () => { const val1 = ['ImDb top 250', 'My birthday', 'jef']; const val2 = ['jos']; const action = RulePossibility.CONTAINS_PARTIAL; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(false); }); @@ -251,7 +237,7 @@ describe('RuleExecutorService', () => { const val1 = ['abc', 'def']; const val2 = ['']; const action = RulePossibility.CONTAINS; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(false); }); @@ -259,7 +245,7 @@ describe('RuleExecutorService', () => { const val1 = ['abc', 'def']; const val2 = ['']; const action = RulePossibility.CONTAINS_PARTIAL; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(false); }); @@ -267,7 +253,7 @@ describe('RuleExecutorService', () => { const val1 = ['abc', 'def']; const val2 = ['ral', undefined, 'rel']; const action = RulePossibility.CONTAINS; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(false); }); @@ -275,7 +261,7 @@ describe('RuleExecutorService', () => { const val1 = ['abc', 'def']; const val2 = ['ral', undefined, 'rel']; const action = RulePossibility.CONTAINS_PARTIAL; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(false); }); @@ -283,7 +269,7 @@ describe('RuleExecutorService', () => { const val1 = ['abc', 'def']; const val2 = ['ral', undefined, 'abc']; const action = RulePossibility.CONTAINS; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(true); }); @@ -291,7 +277,7 @@ describe('RuleExecutorService', () => { const val1 = ['abc', 'def']; const val2 = ['ral', undefined, 'abc']; const action = RulePossibility.CONTAINS_PARTIAL; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(true); }); @@ -299,7 +285,7 @@ describe('RuleExecutorService', () => { const val1 = ['abc', 'def']; const val2 = ['ral', undefined, 'ab']; const action = RulePossibility.CONTAINS_PARTIAL; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(true); }); @@ -307,7 +293,7 @@ describe('RuleExecutorService', () => { const val1 = ['abc', 'def']; const val2 = ['ghi']; const action = RulePossibility.CONTAINS; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(false); }); @@ -315,7 +301,7 @@ describe('RuleExecutorService', () => { const val1 = ['abc', 'def']; const val2 = ['ghi']; const action = RulePossibility.NOT_CONTAINS; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(true); }); @@ -323,7 +309,7 @@ describe('RuleExecutorService', () => { const val1 = ['ImDb top 250', 'My birthday', 'jef']; const val2 = ['ImDb']; const action = RulePossibility.NOT_CONTAINS; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(true); }); @@ -331,7 +317,7 @@ describe('RuleExecutorService', () => { const val1 = ['ImDb top 250', 'My birthday', 'jef']; const val2 = ['ImDb']; const action = RulePossibility.NOT_CONTAINS_PARTIAL; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(false); }); @@ -339,7 +325,7 @@ describe('RuleExecutorService', () => { const val1 = ['ImDb top 250', 'My birthday', 'jef']; const val2 = ['Jos']; const action = RulePossibility.NOT_CONTAINS; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(true); }); @@ -347,7 +333,7 @@ describe('RuleExecutorService', () => { const val1 = ['ImDb top 250', 'My birthday', 'jef']; const val2 = ['Jos']; const action = RulePossibility.NOT_CONTAINS_PARTIAL; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(true); }); @@ -355,7 +341,7 @@ describe('RuleExecutorService', () => { const val1 = ['abc', 'def']; const val2 = ['abc']; const action = RulePossibility.NOT_CONTAINS; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(false); }); @@ -363,7 +349,7 @@ describe('RuleExecutorService', () => { const val1 = ['abc', 'def']; const val2 = ['abc']; const action = RulePossibility.NOT_CONTAINS_PARTIAL; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(false); }); @@ -371,7 +357,7 @@ describe('RuleExecutorService', () => { const val1 = [1, 2, 3, 4]; const val2 = [6]; const action = RulePossibility.CONTAINS; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(false); }); @@ -379,7 +365,7 @@ describe('RuleExecutorService', () => { const val1 = [1, 2, 3, 4]; const val2 = [6]; const action = RulePossibility.CONTAINS_PARTIAL; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(false); }); @@ -387,7 +373,7 @@ describe('RuleExecutorService', () => { const val1 = [1, 2, 3, 4]; const val2 = [6]; const action = RulePossibility.NOT_CONTAINS; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(true); }); @@ -395,7 +381,7 @@ describe('RuleExecutorService', () => { const val1 = [1, 2, 3, 4]; const val2 = [5, undefined, 6]; const action = RulePossibility.NOT_CONTAINS; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(true); }); @@ -403,7 +389,7 @@ describe('RuleExecutorService', () => { const val1 = [1, 2, 3, 4]; const val2 = [3]; const action = RulePossibility.CONTAINS; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(true); }); @@ -411,7 +397,7 @@ describe('RuleExecutorService', () => { const val1 = [1, 2, 3, 4]; const val2 = [3]; const action = RulePossibility.NOT_CONTAINS; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(false); }); @@ -419,7 +405,7 @@ describe('RuleExecutorService', () => { const val1 = [1, 2, 3, 4]; const val2 = [6, 5]; const action = RulePossibility.CONTAINS; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(false); }); @@ -427,7 +413,7 @@ describe('RuleExecutorService', () => { const val1 = [1, 2, 3, 4]; const val2 = [3, 1]; const action = RulePossibility.CONTAINS; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(true); }); @@ -435,7 +421,7 @@ describe('RuleExecutorService', () => { const val1 = [1, 2, 3, 4]; const val2 = [3, 5]; const action = RulePossibility.NOT_CONTAINS; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(false); }); @@ -443,7 +429,7 @@ describe('RuleExecutorService', () => { const val1 = 5; const val2 = 3; const action = RulePossibility.BIGGER; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(true); }); @@ -451,7 +437,7 @@ describe('RuleExecutorService', () => { const val1 = 5; const val2 = 3; const action = RulePossibility.SMALLER; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(false); }); @@ -459,7 +445,7 @@ describe('RuleExecutorService', () => { const val1 = 5; const val2 = undefined; const action = RulePossibility.SMALLER; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(false); }); @@ -467,7 +453,7 @@ describe('RuleExecutorService', () => { const val1 = 5; const val2 = 5; const action = RulePossibility.EQUALS; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(true); }); @@ -475,7 +461,7 @@ describe('RuleExecutorService', () => { const val1 = 5; const val2 = 5; const action = RulePossibility.NOT_EQUALS; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(false); }); @@ -483,7 +469,7 @@ describe('RuleExecutorService', () => { const val1 = new Date('2022-01-01'); const val2 = new Date('2022-01-02'); const action = RulePossibility.BEFORE; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(true); }); @@ -491,7 +477,7 @@ describe('RuleExecutorService', () => { const val1 = new Date('2022-01-01'); const val2 = undefined; const action = RulePossibility.BEFORE; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(false); }); @@ -499,7 +485,7 @@ describe('RuleExecutorService', () => { const val1 = new Date('2022-01-03'); const val2 = new Date('2022-01-02'); const action = RulePossibility.BEFORE; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(false); }); @@ -507,7 +493,7 @@ describe('RuleExecutorService', () => { const val1 = new Date('2022-01-03'); const val2 = new Date('2022-01-02'); const action = RulePossibility.AFTER; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(true); }); @@ -515,7 +501,7 @@ describe('RuleExecutorService', () => { const val1 = new Date('2022-01-01'); const val2 = new Date('2022-01-02'); const action = RulePossibility.AFTER; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(false); }); @@ -523,7 +509,7 @@ describe('RuleExecutorService', () => { const val1 = new Date('2022-01-01'); const val2 = undefined; const action = RulePossibility.AFTER; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(false); }); @@ -531,7 +517,7 @@ describe('RuleExecutorService', () => { const val1 = new Date(Date.now() - 1000); // One second ago const val2 = new Date(new Date().getTime() - +3600 * 1000); // 1 hour in seconds const action = RulePossibility.IN_LAST; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(true); }); @@ -539,7 +525,7 @@ describe('RuleExecutorService', () => { const val1 = new Date(Date.now() - 3600 * 2000); // More than 1 hour ago const val2 = new Date(new Date().getTime() - +3600 * 1000); const action = RulePossibility.IN_LAST; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(false); }); @@ -547,7 +533,7 @@ describe('RuleExecutorService', () => { const val1 = new Date(Date.now() - 3600 * 2000); // More than 1 hour ago const val2 = undefined; const action = RulePossibility.IN_LAST; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(false); }); @@ -555,7 +541,7 @@ describe('RuleExecutorService', () => { const val1 = new Date(new Date().getTime() + +432000 * 1000); // 5 days from now const val2 = new Date(new Date().getTime() + +864000 * 1000); // 10 days from now const action = RulePossibility.IN_NEXT; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(true); }); @@ -563,7 +549,7 @@ describe('RuleExecutorService', () => { const val1 = new Date(new Date().getTime() + +865000 * 1000); // More than 10 days from now const val2 = new Date(new Date().getTime() + +864000 * 1000); // 10 days from now const action = RulePossibility.IN_NEXT; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(false); }); @@ -571,7 +557,7 @@ describe('RuleExecutorService', () => { const val1 = new Date(new Date().getTime() + +865000 * 1000); // More than 10 days from now const val2 = undefined; // 10 days from now const action = RulePossibility.IN_NEXT; - const result = ruleExecutorService['doRuleAction'](val1, val2, action); + const result = ruleComparatorService['doRuleAction'](val1, val2, action); expect(result).toBe(false); }); }); diff --git a/ui/src/components/Rules/RuleGroup/AddModal/index.tsx b/ui/src/components/Rules/RuleGroup/AddModal/index.tsx index 68355d1b..32b54a8d 100644 --- a/ui/src/components/Rules/RuleGroup/AddModal/index.tsx +++ b/ui/src/components/Rules/RuleGroup/AddModal/index.tsx @@ -705,7 +705,7 @@ const AddModal = (props: AddModal) => { -
+
- {/* -
-
- {}, 5000)} - text="Handle collection" - /> -
-
*/} + +
= ( })} />
+ + {mediaTestModalOpen && props.collection?.id ? ( + { + setMediaTestModalOpen(false) + }} + onSubmit={() => {}} + /> + ) : undefined}
) } diff --git a/ui/src/components/Common/SearchMediaITem/index.tsx b/ui/src/components/Common/SearchMediaITem/index.tsx new file mode 100644 index 00000000..fcfa367c --- /dev/null +++ b/ui/src/components/Common/SearchMediaITem/index.tsx @@ -0,0 +1,52 @@ +import React from 'react' +import AsyncSelect from 'react-select/async' +import GetApiHandler from '../../../utils/ApiHandler' +import { IPlexMetadata } from '../../Overview/Content' +import { EPlexDataType } from '../../../utils/PlexDataType-enum' +import { SingleValue } from 'react-select/dist/declarations/src' + +export interface IMediaOptions { + id: string + name: string + type: EPlexDataType +} + +interface ISearchMediaITem { + onChange: (item: SingleValue) => void +} + +const SearchMediaItem = (props: ISearchMediaITem) => { + const loadData = async (query: string): Promise => { + // load your data using query + const resp: IPlexMetadata[] = await GetApiHandler(`/plex//search/${query}`) + const output = resp.map((el) => { + return { + id: el.ratingKey, + name: el.title, + type: el.type === 'movie' ? 1 : 2, + } as unknown as IMediaOptions + }) + return output + } + + return ( + <> + option.name} + getOptionValue={(option: IMediaOptions) => option.id} + defaultValue={[]} + defaultOptions={undefined} + loadOptions={loadData} + placeholder='Search... ' + onChange={(selectedItem) => { + props.onChange(selectedItem) + }} + /> + + ) +} + +export default SearchMediaItem diff --git a/ui/src/components/Common/YamlImporterModal/index.tsx b/ui/src/components/Common/YamlImporterModal/index.tsx index e05a88f6..5ecab4d9 100644 --- a/ui/src/components/Common/YamlImporterModal/index.tsx +++ b/ui/src/components/Common/YamlImporterModal/index.tsx @@ -48,6 +48,7 @@ const YamlImporterModal = (props: IYamlImporterModal) => { iconSvg={''} > Date: Sun, 14 Jan 2024 14:19:47 +0100 Subject: [PATCH 04/14] chore: UI - button margin --- ui/src/components/Collection/CollectionDetail/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/components/Collection/CollectionDetail/index.tsx b/ui/src/components/Collection/CollectionDetail/index.tsx index 1409732f..cbb46da3 100644 --- a/ui/src/components/Collection/CollectionDetail/index.tsx +++ b/ui/src/components/Collection/CollectionDetail/index.tsx @@ -141,7 +141,7 @@ const CollectionDetail: React.FC = ( From f05544fd9a6b22a0ea71e42f5e136996ddf2f71b Mon Sep 17 00:00:00 2001 From: jorenn92 Date: Mon, 15 Jan 2024 10:04:48 +0100 Subject: [PATCH 09/14] refactor: button caps --- ui/src/components/Collection/CollectionDetail/index.tsx | 2 +- ui/src/components/Collection/CollectionOverview/index.tsx | 2 +- ui/src/components/Rules/Rule/RuleCreator/index.tsx | 2 +- ui/src/components/Rules/index.tsx | 5 ++--- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/ui/src/components/Collection/CollectionDetail/index.tsx b/ui/src/components/Collection/CollectionDetail/index.tsx index cbb46da3..13736a36 100644 --- a/ui/src/components/Collection/CollectionDetail/index.tsx +++ b/ui/src/components/Collection/CollectionDetail/index.tsx @@ -145,7 +145,7 @@ const CollectionDetail: React.FC = ( onClick={() => setMediaTestModalOpen(true)} > {}{' '} -

Test media

+

Test Media

{
diff --git a/ui/src/components/Rules/Rule/RuleCreator/index.tsx b/ui/src/components/Rules/Rule/RuleCreator/index.tsx index 77450b50..b88661b0 100644 --- a/ui/src/components/Rules/Rule/RuleCreator/index.tsx +++ b/ui/src/components/Rules/Rule/RuleCreator/index.tsx @@ -285,7 +285,7 @@ const RuleCreator = (props: iRuleCreator) => { > {}

- New section + New Section

diff --git a/ui/src/components/Rules/index.tsx b/ui/src/components/Rules/index.tsx index b8c79afa..90c2dbb7 100644 --- a/ui/src/components/Rules/index.tsx +++ b/ui/src/components/Rules/index.tsx @@ -1,7 +1,6 @@ import { RefreshIcon } from '@heroicons/react/outline' import { PlayIcon } from '@heroicons/react/solid' import { debounce } from 'lodash' -import Image from 'next/image' import React, { useContext, useEffect, useState } from 'react' import LibrariesContext from '../../contexts/libraries-context' import GetApiHandler, { PostApiHandler } from '../../utils/ApiHandler' @@ -99,10 +98,10 @@ const Rules: React.FC = () => {
- +
- +
From a85c4ac547c1ce0d606d764058ee38895ee0ca4b Mon Sep 17 00:00:00 2001 From: jorenn92 Date: Mon, 15 Jan 2024 11:53:15 +0100 Subject: [PATCH 10/14] refactor: import/export : fetch identifier from id instead of array location --- .../rules/constants/constants.service.ts | 17 ++++++++++++----- .../rules/getter/radarr-getter.service.ts | 8 ++++---- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/server/src/modules/rules/constants/constants.service.ts b/server/src/modules/rules/constants/constants.service.ts index b10fab46..74f466a0 100644 --- a/server/src/modules/rules/constants/constants.service.ts +++ b/server/src/modules/rules/constants/constants.service.ts @@ -19,16 +19,23 @@ export class RuleConstanstService { } public getValueIdentifier(location: [number, number]) { - const application = this.ruleConstants.applications[location[0]].name; - const rule = - this.ruleConstants.applications[location[0]].props[location[1]].name; + const application = this.ruleConstants.applications.find( + (el) => el.id === location[0], + )?.name; + + const rule = this.ruleConstants.applications + .find((el) => el.id === location[0]) + ?.props.find((el) => el.id === location[1])?.name; return application + '.' + rule; } public getValueHumanName(location: [number, number]) { - return `${this.ruleConstants.applications[location[0]].name} - ${this - .ruleConstants.applications[location[0]].props[location[1]]?.humanName}`; + return `${this.ruleConstants.applications.find( + (el) => el.id === location[0], + )?.name} - ${this.ruleConstants.applications + .find((el) => el.id === location[0]) + ?.props.find((el) => el.id === location[1])?.humanName}`; } public getValueFromIdentifier(identifier: string): [number, number] { diff --git a/server/src/modules/rules/getter/radarr-getter.service.ts b/server/src/modules/rules/getter/radarr-getter.service.ts index 74afb1cf..fd6a2aaf 100644 --- a/server/src/modules/rules/getter/radarr-getter.service.ts +++ b/server/src/modules/rules/getter/radarr-getter.service.ts @@ -104,10 +104,10 @@ export class RadarrGetterService { ? new Date(movieResponse.digitalRelease) : new Date(movieResponse.physicalRelease) : movieResponse.physicalRelease - ? new Date(movieResponse.physicalRelease) - : movieResponse.digitalRelease - ? new Date(movieResponse.digitalRelease) - : null; + ? new Date(movieResponse.physicalRelease) + : movieResponse.digitalRelease + ? new Date(movieResponse.digitalRelease) + : null; } case 'inCinemas': { return movieResponse?.inCinemas From 797b834c692034c9b825cc7a302e3891562da31f Mon Sep 17 00:00:00 2001 From: jorenn92 Date: Mon, 15 Jan 2024 16:56:40 +0100 Subject: [PATCH 11/14] refactor: Responsive monaco editor height --- .../CollectionDetail/TestMediaItem/index.tsx | 179 +++++++++--------- 1 file changed, 88 insertions(+), 91 deletions(-) diff --git a/ui/src/components/Collection/CollectionDetail/TestMediaItem/index.tsx b/ui/src/components/Collection/CollectionDetail/TestMediaItem/index.tsx index 76195be1..37567fbd 100644 --- a/ui/src/components/Collection/CollectionDetail/TestMediaItem/index.tsx +++ b/ui/src/components/Collection/CollectionDetail/TestMediaItem/index.tsx @@ -181,101 +181,98 @@ const TestMediaItem = (props: ITestMediaItem) => { title={'Test Media'} iconSvg={''} > -
- - {`Search for media items and validate them against the specified rule. The result will be a YAML document containing the validated steps. +
+
+ + {`Search for media items and validate them against the specified rule. The result will be a YAML document containing the validated steps. `} -
-
- {`The rule group is of type ${ - ruleGroup.dataType === EPlexDataType.MOVIES - ? 'movies' - : ruleGroup.dataType === EPlexDataType.SEASONS - ? 'seasons' - : ruleGroup.dataType === EPlexDataType.EPISODES - ? 'episodes' - : 'series' - }, as a result only media of type ${ - ruleGroup.dataType === EPlexDataType.MOVIES ? 'movies' : 'series' - } will be displayed in the searchbar.`} -
-
- - { - setMediaItem(el as unknown as IMediaOptions) - }} - /> - +
+
+ {`The rule group is of type ${ + ruleGroup.dataType === EPlexDataType.MOVIES + ? 'movies' + : ruleGroup.dataType === EPlexDataType.SEASONS + ? 'seasons' + : ruleGroup.dataType === EPlexDataType.EPISODES + ? 'episodes' + : 'series' + }, as a result only media of type ${ + ruleGroup.dataType === EPlexDataType.MOVIES + ? 'movies' + : 'series' + } will be displayed in the searchbar.`} + +
+ + { + setMediaItem(el as unknown as IMediaOptions) + }} + /> + - {/* seasons */} -
- {ruleGroup.dataType === EPlexDataType.SEASONS || - ruleGroup.dataType === EPlexDataType.EPISODES ? ( - - - - ) : undefined} + {/* seasons */} +
+ {ruleGroup.dataType === EPlexDataType.SEASONS || + ruleGroup.dataType === EPlexDataType.EPISODES ? ( + + + + ) : undefined} - {ruleGroup.dataType === EPlexDataType.EPISODES ? ( - // episodes - - - - ) : undefined} -
+ {ruleGroup.dataType === EPlexDataType.EPISODES ? ( + // episodes + + + + ) : undefined} +
- -
- + +
+ +
From 7ee28872b7ad585ddb4928a92e705026ea145a5a Mon Sep 17 00:00:00 2001 From: jorenn92 Date: Mon, 15 Jan 2024 17:21:24 +0100 Subject: [PATCH 12/14] fix(collection): Clicking on the card will now navigate to the details page --- .../Collection/CollectionItem/index.tsx | 159 +++++++++--------- 1 file changed, 84 insertions(+), 75 deletions(-) diff --git a/ui/src/components/Collection/CollectionItem/index.tsx b/ui/src/components/Collection/CollectionItem/index.tsx index 9a09d7f5..8a5027ca 100644 --- a/ui/src/components/Collection/CollectionItem/index.tsx +++ b/ui/src/components/Collection/CollectionItem/index.tsx @@ -13,90 +13,99 @@ const CollectionItem = (props: ICollectionItem) => { return ( <> - {/*
*/} - {props.collection.media && props.collection.media.length > 1 ? ( -
- - -
-
- ) : undefined} -
-
- props.onClick(props.collection)} - {...(props.onClick - ? { onClick: () => props.onClick!(props.collection) } - : {})} - > + props.onClick(props.collection)} + {...(props.onClick + ? { onClick: () => props.onClick!(props.collection) } + : {})} + > + {/*
*/} + {props.collection.media && props.collection.media.length > 1 ? ( +
+ + +
+
+ ) : undefined} +
+ +
{props.collection.manualCollection - ? `${props.collection.manualCollectionName} (manual)` - : props.collection.title} - -
-
- {props.collection.manualCollection - ? `Handled by rule: '${props.collection.title}'` - : props.collection.description} + ? `Handled by rule: '${props.collection.title}'` + : props.collection.description} +
-
-
-
-
-

Library

-

- {' '} - { - LibrariesCtx.libraries.find( - (el) => +el.key === +props.collection.libraryId, - )?.title - } -

-
+
+
+
+

Library

+

+ {' '} + { + LibrariesCtx.libraries.find( + (el) => +el.key === +props.collection.libraryId, + )?.title + } +

+
-
-

Items

-

- {' '} - {`${props.collection.media ? props.collection.media.length : 0}`} -

+
+

Items

+

+ {' '} + {`${ + props.collection.media ? props.collection.media.length : 0 + }`} +

+
-
-
-
-

Status

-

- {' '} - {props.collection.isActive ? ( - Active - ) : ( - Inactive - )} -

-
+
+
+

Status

+

+ {' '} + {props.collection.isActive ? ( + Active + ) : ( + Inactive + )} +

+
-
-

Delete

-

{` After ${props.collection.deleteAfterDays} days`}

+
+

Delete

+

{` After ${props.collection.deleteAfterDays} days`}

+
-
- {/*
*/} + ) } From c1c38500c8bd982fbe63875eb24b75a14f6a3e56 Mon Sep 17 00:00:00 2001 From: Jorenn92 Date: Mon, 15 Jan 2024 19:40:31 +0100 Subject: [PATCH 13/14] feat(collection): Expanded collection detail screen, it now shows exclusions --- .../collections/collections.controller.ts | 17 +++ .../modules/collections/collections.module.ts | 8 +- .../collections/collections.service.ts | 60 +++++++- .../rules/entities/exclusion.entities.ts | 3 + .../CollectionDetail/Exclusions/index.tsx | 138 ++++++++++++++++++ .../Collection/CollectionDetail/index.tsx | 95 ++++++++---- .../Collection/CollectionOverview/index.tsx | 5 +- .../components/Common/TabbedLinks/index.tsx | 72 +++++++++ ui/src/components/Settings/Tabs/index.tsx | 4 +- 9 files changed, 365 insertions(+), 37 deletions(-) create mode 100644 ui/src/components/Collection/CollectionDetail/Exclusions/index.tsx create mode 100644 ui/src/components/Common/TabbedLinks/index.tsx diff --git a/server/src/modules/collections/collections.controller.ts b/server/src/modules/collections/collections.controller.ts index 91ee597f..d3d44ade 100644 --- a/server/src/modules/collections/collections.controller.ts +++ b/server/src/modules/collections/collections.controller.ts @@ -153,4 +153,21 @@ export class CollectionsController { size: size, }); } + + @Get('/exclusions/:id/content/:page') + getExclusions( + @Param('id') id: number, + @Param('page', new ParseIntPipe()) page: number, + @Query('size') amount: number, + ) { + const size = amount ? amount : 25; + const offset = (page - 1) * size; + return this.collectionService.getCollectionExclusionsWithPlexDataAndhPaging( + id, + { + offset: offset, + size: size, + }, + ); + } } diff --git a/server/src/modules/collections/collections.module.ts b/server/src/modules/collections/collections.module.ts index 475fd146..a40ff672 100644 --- a/server/src/modules/collections/collections.module.ts +++ b/server/src/modules/collections/collections.module.ts @@ -11,11 +11,17 @@ import { OverseerrApiModule } from '../api/overseerr-api/overseerr-api.module'; import { ServarrApiModule } from '../api/servarr-api/servarr-api.module'; import { RuleGroup } from '../rules/entities/rule-group.entities'; import { TasksModule } from '../tasks/tasks.module'; +import { Exclusion } from '../rules/entities/exclusion.entities'; @Module({ imports: [ PlexApiModule, - TypeOrmModule.forFeature([Collection, CollectionMedia, RuleGroup]), + TypeOrmModule.forFeature([ + Collection, + CollectionMedia, + RuleGroup, + Exclusion, + ]), OverseerrApiModule, TmdbApiModule, ServarrApiModule, diff --git a/server/src/modules/collections/collections.service.ts b/server/src/modules/collections/collections.service.ts index 2e1b6c26..74e11cae 100644 --- a/server/src/modules/collections/collections.service.ts +++ b/server/src/modules/collections/collections.service.ts @@ -23,6 +23,7 @@ import { } from './interfaces/collection-media.interface'; import { ICollection } from './interfaces/collection.interface'; import { EPlexDataType } from '../api/plex-api/enums/plex-data-type-enum'; +import { Exclusion } from '../rules/entities/exclusion.entities'; interface addCollectionDbResponse { id: number; @@ -41,6 +42,8 @@ export class CollectionsService { private readonly CollectionMediaRepo: Repository, @InjectRepository(RuleGroup) private readonly ruleGroupRepo: Repository, + @InjectRepository(Exclusion) + private readonly exclusionRepo: Repository, private readonly connection: Connection, private readonly plexApi: PlexApiService, private readonly tmdbApi: TmdbApiService, @@ -120,14 +123,67 @@ export class CollectionsService { } } + public async getCollectionExclusionsWithPlexDataAndhPaging( + id: number, + { offset = 0, size = 25 }: { offset?: number; size?: number } = {}, + ): Promise<{ totalSize: number; items: Exclusion[] }> { + try { + const rulegroup = await this.ruleGroupRepo.findOne({ + where: { + collectionId: id, + }, + }); + + const groupId = rulegroup.id; + + const queryBuilder = this.exclusionRepo.createQueryBuilder('exclusion'); + + queryBuilder + .where(`exclusion.ruleGroupId = ${groupId}`) + .orWhere(`exclusion.ruleGroupId is null`) + .orderBy('id', 'DESC') + .skip(offset) + .take(size); + + const itemCount = await queryBuilder.getCount(); + let { entities } = await queryBuilder.getRawAndEntities(); + + entities = await Promise.all( + entities.map(async (el) => { + el.plexData = await this.plexApi.getMetadata(el.plexId.toString()); + if (el.plexData?.grandparentRatingKey) { + el.plexData.parentData = await this.plexApi.getMetadata( + el.plexData.grandparentRatingKey.toString(), + ); + } else if (el.plexData?.parentRatingKey) { + el.plexData.parentData = await this.plexApi.getMetadata( + el.plexData.parentRatingKey.toString(), + ); + } + return el; + }), + ); + + return { + totalSize: itemCount, + items: entities ?? [], + }; + } catch (err) { + this.logger.warn( + 'An error occurred while performing collection actions: ' + err, + ); + return undefined; + } + } + async getCollections(libraryId?: number, typeId?: 1 | 2 | 3 | 4) { try { const collections = await this.collectionRepo.find( libraryId ? { where: { libraryId: libraryId } } : typeId - ? { where: { type: typeId } } - : undefined, + ? { where: { type: typeId } } + : undefined, ); return await Promise.all( diff --git a/server/src/modules/rules/entities/exclusion.entities.ts b/server/src/modules/rules/entities/exclusion.entities.ts index 5d9753d5..97e0045e 100644 --- a/server/src/modules/rules/entities/exclusion.entities.ts +++ b/server/src/modules/rules/entities/exclusion.entities.ts @@ -1,3 +1,4 @@ +import { PlexMetadata } from 'src/modules/api/plex-api/interfaces/media.interface'; import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'; @Entity() @@ -13,4 +14,6 @@ export class Exclusion { @Column({ nullable: true }) parent: number; + + plexData: PlexMetadata; // this will be added programatically } diff --git a/ui/src/components/Collection/CollectionDetail/Exclusions/index.tsx b/ui/src/components/Collection/CollectionDetail/Exclusions/index.tsx new file mode 100644 index 00000000..aca2b204 --- /dev/null +++ b/ui/src/components/Collection/CollectionDetail/Exclusions/index.tsx @@ -0,0 +1,138 @@ +import { useEffect, useRef, useState } from 'react' +import OverviewContent, { IPlexMetadata } from '../../../Overview/Content' +import _ from 'lodash' +import { ICollection, ICollectionMedia } from '../..' +import GetApiHandler from '../../../../utils/ApiHandler' + +interface ICollectionExclusions { + collection: ICollection + libraryId: number +} + +const CollectionExcludions = (props: ICollectionExclusions) => { + const [data, setData] = useState([]) + const [media, setMedia] = useState([]) + // paging + const pageData = useRef(0) + const fetchAmount = 25 + const [totalSize, setTotalSize] = useState(999) + const totalSizeRef = useRef(999) + const dataRef = useRef([]) + const mediaRef = useRef([]) + const loadingRef = useRef(true) + const loadingExtraRef = useRef(false) + const [page, setPage] = useState(0) + + useEffect(() => { + // Initial first fetch + setPage(1) + }, []) + + const handleScroll = () => { + if ( + window.innerHeight + document.documentElement.scrollTop >= + document.documentElement.scrollHeight * 0.9 + ) { + if ( + !loadingRef.current && + !loadingExtraRef.current && + !(fetchAmount * (pageData.current - 1) >= totalSizeRef.current) + ) { + setPage(pageData.current + 1) + } + } + } + + useEffect(() => { + if (page !== 0) { + // Ignore initial page render + pageData.current = pageData.current + 1 + fetchData() + } + }, [page]) + + useEffect(() => { + window.addEventListener('scroll', _.debounce(handleScroll.bind(this), 200)) + return () => { + window.removeEventListener( + 'scroll', + _.debounce(handleScroll.bind(this), 200), + ) + } + }, []) + + const fetchData = async () => { + if (!loadingRef.current) { + loadingExtraRef.current = true + } + // setLoading(true) + const resp: { totalSize: number; items: ICollectionMedia[] } = + await GetApiHandler( + `/collections/exclusions/${props.collection.id}/content/${pageData.current}?size=${fetchAmount}`, + ) + + setTotalSize(resp.totalSize) + // pageData.current = pageData.current + 1 + setMedia([...mediaRef.current, ...resp.items]) + + setData([ + ...dataRef.current, + ...resp.items.map((el) => + el.plexData ? el.plexData : ({} as IPlexMetadata), + ), + ]) + loadingRef.current = false + loadingExtraRef.current = false + } + + useEffect(() => { + dataRef.current = data + + // If page is not filled yet, fetch more + if ( + !loadingRef.current && + !loadingExtraRef.current && + window.innerHeight + document.documentElement.scrollTop >= + document.documentElement.scrollHeight * 0.9 && + !(fetchAmount * (pageData.current - 1) >= totalSizeRef.current) + ) { + setPage(page + 1) + } + }, [data]) + + useEffect(() => { + mediaRef.current = media + }, [media]) + + useEffect(() => { + totalSizeRef.current = totalSize + }, [totalSize]) + + return ( + {}} + loading={loadingRef.current} + data={data} + libraryId={props.libraryId} + collectionPage={true} + extrasLoading={ + loadingExtraRef && + !loadingRef.current && + totalSize >= pageData.current * fetchAmount + } + onRemove={(id: string) => + setTimeout(() => { + setData(dataRef.current.filter((el) => +el.ratingKey !== +id)) + setMedia(mediaRef.current.filter((el) => +el.plexId !== +id)) + }, 500) + } + collectionInfo={media.map((el) => { + props.collection.media = [] + el.collection = props.collection + return el + })} + /> + ) +} +export default CollectionExcludions diff --git a/ui/src/components/Collection/CollectionDetail/index.tsx b/ui/src/components/Collection/CollectionDetail/index.tsx index 13736a36..bf483639 100644 --- a/ui/src/components/Collection/CollectionDetail/index.tsx +++ b/ui/src/components/Collection/CollectionDetail/index.tsx @@ -6,6 +6,8 @@ import GetApiHandler from '../../../utils/ApiHandler' import OverviewContent, { IPlexMetadata } from '../../Overview/Content' import _ from 'lodash' import TestMediaItem from './TestMediaItem' +import TabbedLinks, { TabbedRoute } from '../../Common/TabbedLinks' +import CollectionExcludions from './Exclusions' interface ICollectionDetail { libraryId: number @@ -20,6 +22,7 @@ const CollectionDetail: React.FC = ( ) => { const [data, setData] = useState([]) const [media, setMedia] = useState([]) + const [selectedTab, setSelectedTab] = useState('media') const [mediaTestModalOpen, setMediaTestModalOpen] = useState(false) // paging const pageData = useRef(0) @@ -132,6 +135,17 @@ const CollectionDetail: React.FC = ( } }, []) + const tabbedRoutes: TabbedRoute[] = [ + { + text: 'Media', + route: 'media', + }, + { + text: 'Exclusions', + route: 'exclusions', + }, + ] + return (
@@ -140,38 +154,57 @@ const CollectionDetail: React.FC = (
-
- {}} - loading={loadingRef.current} - data={data} - libraryId={props.libraryId} - collectionPage={true} - extrasLoading={ - loadingExtraRef && - !loadingRef.current && - totalSize >= pageData.current * fetchAmount - } - onRemove={(id: string) => - setTimeout(() => { - setData(dataRef.current.filter((el) => +el.ratingKey !== +id)) - setMedia(mediaRef.current.filter((el) => +el.plexId !== +id)) - }, 500) - } - collectionInfo={media.map((el) => { - props.collection.media = [] - el.collection = props.collection - return el - })} - /> +
+
+ setSelectedTab(t)} + routes={tabbedRoutes} + currentRoute={selectedTab} + allEnabled={true} + /> +
+
+ + + + {selectedTab === 'media' ? ( + {}} + loading={loadingRef.current} + data={data} + libraryId={props.libraryId} + collectionPage={true} + extrasLoading={ + loadingExtraRef && + !loadingRef.current && + totalSize >= pageData.current * fetchAmount + } + onRemove={(id: string) => + setTimeout(() => { + setData(dataRef.current.filter((el) => +el.ratingKey !== +id)) + setMedia(mediaRef.current.filter((el) => +el.plexId !== +id)) + }, 500) + } + collectionInfo={media.map((el) => { + props.collection.media = [] + el.collection = props.collection + return el + })} + /> + ) : selectedTab === 'exclusions' ? ( + + ) : undefined}
{mediaTestModalOpen && props.collection?.id ? ( diff --git a/ui/src/components/Collection/CollectionOverview/index.tsx b/ui/src/components/Collection/CollectionOverview/index.tsx index ce2de5cc..57696528 100644 --- a/ui/src/components/Collection/CollectionOverview/index.tsx +++ b/ui/src/components/Collection/CollectionOverview/index.tsx @@ -33,7 +33,10 @@ const CollectionOverview = (props: ICollectionOverview) => {
    {props.collections?.map((col) => ( -
  • +
  • void +} + +export interface ITabbedLink { + currentRoute: string + route: string + hidden?: boolean + disabled?: boolean + children?: ReactNode + onClick: (path: string) => void +} + +const TabbedLink = (props: ITabbedLink) => { + let linkClasses = + (props.disabled ? 'pointer-events-none touch-none ' : 'cursor-pointer ') + + 'px-1 py-4 ml-8 text-md font-semibold leading-5 transition duration-300 leading-5 whitespace-nowrap first:ml-0' + let activeLinkColor = ' border-b text-amber-500 border-amber-600' + let inactiveLinkColor = + 'border-transparent text-zinc-500 hover:border-b focus:border-b hover:text-zinc-300 hover:border-zinc-400 focus:text-zinc-300 focus:border-zinc-400' + + return ( + props.onClick(props.route)} + className={`${linkClasses} ${ + props.currentRoute.match(props.route) + ? activeLinkColor + : inactiveLinkColor + }`} + aria-current="page" + > + {props.children} + + ) +} + +const TabbedLinks = (props: ItabbedLinks) => { + return ( + <> +
    + +
    + + ) +} + +export default TabbedLinks diff --git a/ui/src/components/Settings/Tabs/index.tsx b/ui/src/components/Settings/Tabs/index.tsx index efdae3e9..de79bf80 100644 --- a/ui/src/components/Settings/Tabs/index.tsx +++ b/ui/src/components/Settings/Tabs/index.tsx @@ -33,8 +33,8 @@ const SettingsLink: React.FC = (props: ISettingsLink) => { let linkClasses = (props.disabled ? 'pointer-events-none touch-none ' : '') + - 'px-1 py-4 ml-8 text-sm font-medium leading-5 transition duration-300 border-b-2 border-transparent whitespace-nowrap first:ml-0' - let activeLinkColor = 'text-amber-500 border-amber-600' + 'px-1 py-4 ml-8 text-sm font-medium leading-5 transition duration-300 border-b-2 whitespace-nowrap first:ml-0' + let activeLinkColor = 'text-amber-500 border-amber-600 border-b' let inactiveLinkColor = 'text-zinc-500 border-transparent hover:text-zinc-300 hover:border-zinc-400 focus:text-zinc-300 focus:border-zinc-400' From 5dc3874703f03fe0f2b1c38a2cbc20be69a06490 Mon Sep 17 00:00:00 2001 From: Joren V <9069536+jorenn92@users.noreply.github.com> Date: Tue, 16 Jan 2024 08:41:43 +0100 Subject: [PATCH 14/14] =?UTF-8?q?feat:=20Added=20the=20ability=20to=20test?= =?UTF-8?q?=20media=20items=20against=20a=20rule,=20returning=E2=80=A6=20(?= =?UTF-8?q?#758)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Added the ability to test media items against a rule, returning a detailed execution breakdown * chore: Add backend code for rule statistics. Also add an API endpoint to test a single media item * refactor: Add application to rule statistics output name * chore: Add UI modal * chore: UI - button margin * refactor: Fix broken seasons / episodes after rule comparator refactor * refactor: Add a global statistic result indicating if the item matched the complete rule * refactor: add info header * refactor: Added validation for media type & improved info messages * refactor: button caps * refactor: import/export : fetch identifier from id instead of array location * refactor: Responsive monaco editor height * fix(collection): Clicking on the card will now navigate to the details page * build(deps-dev): bump @nestjs/cli from 10.2.1 to 10.3.0 Bumps [@nestjs/cli](https://github.com/nestjs/nest-cli) from 10.2.1 to 10.3.0. - [Release notes](https://github.com/nestjs/nest-cli/releases) - [Changelog](https://github.com/nestjs/nest-cli/blob/master/.release-it.json) - [Commits](https://github.com/nestjs/nest-cli/compare/10.2.1...10.3.0) --- updated-dependencies: - dependency-name: "@nestjs/cli" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * build(deps): bump @nestjs/common from 10.2.7 to 10.3.0 Bumps [@nestjs/common](https://github.com/nestjs/nest/tree/HEAD/packages/common) from 10.2.7 to 10.3.0. - [Release notes](https://github.com/nestjs/nest/releases) - [Commits](https://github.com/nestjs/nest/commits/v10.3.0/packages/common) --- updated-dependencies: - dependency-name: "@nestjs/common" dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * build(deps-dev): bump postcss from 8.4.31 to 8.4.33 Bumps [postcss](https://github.com/postcss/postcss) from 8.4.31 to 8.4.33. - [Release notes](https://github.com/postcss/postcss/releases) - [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/postcss/postcss/compare/8.4.31...8.4.33) --- updated-dependencies: - dependency-name: postcss dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * build(deps-dev): bump semantic-release from 22.0.5 to 23.0.0 Bumps [semantic-release](https://github.com/semantic-release/semantic-release) from 22.0.5 to 23.0.0. - [Release notes](https://github.com/semantic-release/semantic-release/releases) - [Commits](https://github.com/semantic-release/semantic-release/compare/v22.0.5...v23.0.0) --- updated-dependencies: - dependency-name: semantic-release dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * build(deps): bump typeorm from 0.3.17 to 0.3.19 Bumps [typeorm](https://github.com/typeorm/typeorm) from 0.3.17 to 0.3.19. - [Release notes](https://github.com/typeorm/typeorm/releases) - [Changelog](https://github.com/typeorm/typeorm/blob/master/CHANGELOG.md) - [Commits](https://github.com/typeorm/typeorm/compare/0.3.17...0.3.19) --- updated-dependencies: - dependency-name: typeorm dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * build(deps-dev): bump ts-node from 10.9.1 to 10.9.2 Bumps [ts-node](https://github.com/TypeStrong/ts-node) from 10.9.1 to 10.9.2. - [Release notes](https://github.com/TypeStrong/ts-node/releases) - [Changelog](https://github.com/TypeStrong/ts-node/blob/main/development-docs/release-template.md) - [Commits](https://github.com/TypeStrong/ts-node/compare/v10.9.1...v10.9.2) --- updated-dependencies: - dependency-name: ts-node dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * build(deps-dev): bump eslint-plugin-prettier from 5.0.1 to 5.1.3 Bumps [eslint-plugin-prettier](https://github.com/prettier/eslint-plugin-prettier) from 5.0.1 to 5.1.3. - [Release notes](https://github.com/prettier/eslint-plugin-prettier/releases) - [Changelog](https://github.com/prettier/eslint-plugin-prettier/blob/master/CHANGELOG.md) - [Commits](https://github.com/prettier/eslint-plugin-prettier/compare/v5.0.1...v5.1.3) --- updated-dependencies: - dependency-name: eslint-plugin-prettier dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * build(deps-dev): bump ts-loader from 9.5.0 to 9.5.1 Bumps [ts-loader](https://github.com/TypeStrong/ts-loader) from 9.5.0 to 9.5.1. - [Release notes](https://github.com/TypeStrong/ts-loader/releases) - [Changelog](https://github.com/TypeStrong/ts-loader/blob/main/CHANGELOG.md) - [Commits](https://github.com/TypeStrong/ts-loader/compare/v9.5.0...v9.5.1) --- updated-dependencies: - dependency-name: ts-loader dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * build(deps-dev): bump @nestjs/schematics from 10.0.3 to 10.1.0 Bumps [@nestjs/schematics](https://github.com/nestjs/schematics) from 10.0.3 to 10.1.0. - [Release notes](https://github.com/nestjs/schematics/releases) - [Changelog](https://github.com/nestjs/schematics/blob/master/.release-it.json) - [Commits](https://github.com/nestjs/schematics/compare/10.0.3...10.1.0) --- updated-dependencies: - dependency-name: "@nestjs/schematics" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- package.json | 18 +- .../Collection/CollectionDetail/index.tsx | 3 +- yarn.lock | 629 +++++++----------- 3 files changed, 260 insertions(+), 390 deletions(-) diff --git a/package.json b/package.json index 017ae664..7edb29dd 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "@headlessui/react": "1.7.12", "@heroicons/react": "^1.0.6", "@monaco-editor/react": "^4.6.0", - "@nestjs/common": "^10.2.7", + "@nestjs/common": "^10.3.0", "@nestjs/core": "^10.3.0", "@nestjs/platform-express": "^10.3.0", "@nestjs/schedule": "^4.0.0", @@ -60,7 +60,7 @@ "rxjs": "^7.8.1", "sqlite3": "^5.1.6", "swr": "^2.2.4", - "typeorm": "^0.3.17", + "typeorm": "^0.3.19", "web-push": "^3.6.6", "xml2js": "^0.6.2", "yaml": "^2.3.4" @@ -69,8 +69,8 @@ "@automock/jest": "^1.4.0", "@babel/cli": "^7.23.0", "@babel/core": "^7.23.2", - "@nestjs/cli": "^10.2.1", - "@nestjs/schematics": "^10.0.3", + "@nestjs/cli": "^10.3.0", + "@nestjs/schematics": "^10.1.0", "@nestjs/testing": "^10.2.10", "@semantic-release/changelog": "^6.0.3", "@semantic-release/commit-analyzer": "^11.1.0", @@ -99,19 +99,19 @@ "eslint": "^8.56.0", "eslint-config-next": "14.0.4", "eslint-config-prettier": "^9.1.0", - "eslint-plugin-prettier": "^5.0.1", + "eslint-plugin-prettier": "^5.1.3", "jest": "^29.7.0", "jsdoc": "^4.0.2", - "postcss": "^8.4.31", + "postcss": "^8.4.33", "prettier": "^3.1.1", "prettier-plugin-tailwindcss": "^0.5.6", - "semantic-release": "^22.0.5", + "semantic-release": "^23.0.0", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "tailwindcss": "^3.4.1", "ts-jest": "^29.1.1", - "ts-loader": "^9.5.0", - "ts-node": "^10.9.1", + "ts-loader": "^9.5.1", + "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", "typescript": "^5.3.3" }, diff --git a/ui/src/components/Collection/CollectionDetail/index.tsx b/ui/src/components/Collection/CollectionDetail/index.tsx index bf483639..f7ea7a1c 100644 --- a/ui/src/components/Collection/CollectionDetail/index.tsx +++ b/ui/src/components/Collection/CollectionDetail/index.tsx @@ -22,6 +22,7 @@ const CollectionDetail: React.FC = ( ) => { const [data, setData] = useState([]) const [media, setMedia] = useState([]) + const [selectedTab, setSelectedTab] = useState('media') const [mediaTestModalOpen, setMediaTestModalOpen] = useState(false) // paging @@ -153,7 +154,7 @@ const CollectionDetail: React.FC = ( {`${props.title}`}
- +
diff --git a/yarn.lock b/yarn.lock index 8be20dd0..64a31970 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20,60 +20,38 @@ "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" -"@angular-devkit/core@16.1.8": - version "16.1.8" - resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-16.1.8.tgz#f54bf179a78c6ea83ccd46687c54766d3ba674c9" - integrity sha512-dSRD/+bGanArIXkj+kaU1kDFleZeQMzmBiOXX+pK0Ah9/0Yn1VmY3RZh1zcX9vgIQXV+t7UPrTpOjaERMUtVGw== +"@angular-devkit/core@17.0.9": + version "17.0.9" + resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-17.0.9.tgz#f79ff77fc38e8af1af4694acfb5b480339f073e1" + integrity sha512-r5jqwpWOgowqe9KSDqJ3iSbmsEt2XPjSvRG4DSI2T9s31bReoMtreo8b7wkRa2B3hbcDnstFbn8q27VvJDqRaQ== dependencies: ajv "8.12.0" ajv-formats "2.1.1" jsonc-parser "3.2.0" + picomatch "3.0.1" rxjs "7.8.1" source-map "0.7.4" -"@angular-devkit/core@16.2.8": - version "16.2.8" - resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-16.2.8.tgz#db74f3063e7fd573be7dafd022e8dc10e43140c0" - integrity sha512-PTGozYvh1Bin5lB15PwcXa26Ayd17bWGLS3H8Rs0s+04mUDvfNofmweaX1LgumWWy3nCUTDuwHxX10M3G0wE2g== +"@angular-devkit/schematics-cli@17.0.9": + version "17.0.9" + resolved "https://registry.yarnpkg.com/@angular-devkit/schematics-cli/-/schematics-cli-17.0.9.tgz#6ee0ac81b37cab50e60b49a0541508552a75ab7f" + integrity sha512-tznzzB26sy8jVUlV9HhXcbFYZcIIFMAiDMOuyLko2LZFjfoqW+OPvwa1mwAQwvVVSQZVAKvdndFhzwyl/axwFQ== dependencies: - ajv "8.12.0" - ajv-formats "2.1.1" - jsonc-parser "3.2.0" - picomatch "2.3.1" - rxjs "7.8.1" - source-map "0.7.4" - -"@angular-devkit/schematics-cli@16.2.8": - version "16.2.8" - resolved "https://registry.yarnpkg.com/@angular-devkit/schematics-cli/-/schematics-cli-16.2.8.tgz#5945f391d316724d7b49d578932dcb5a25a73649" - integrity sha512-EXURJCzWTVYCipiTT4vxQQOrF63asOUDbeOy3OtiSh7EwIUvxm3BPG6hquJqngEnI/N6bA75NJ1fBhU6Hrh7eA== - dependencies: - "@angular-devkit/core" "16.2.8" - "@angular-devkit/schematics" "16.2.8" + "@angular-devkit/core" "17.0.9" + "@angular-devkit/schematics" "17.0.9" ansi-colors "4.1.3" - inquirer "8.2.4" + inquirer "9.2.11" symbol-observable "4.0.0" yargs-parser "21.1.1" -"@angular-devkit/schematics@16.1.8": - version "16.1.8" - resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-16.1.8.tgz#ab392d3a71bd50dcde25f9fbcee7e67d3965565d" - integrity sha512-6LyzMdFJs337RTxxkI2U1Ndw0CW5mMX/aXWl8d7cW2odiSrAg8IdlMqpc+AM8+CPfsB0FtS1aWkEZqJLT0jHOg== +"@angular-devkit/schematics@17.0.9": + version "17.0.9" + resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-17.0.9.tgz#8370d21cf0ac0f5f99b7a27fcf626cc5cd682c95" + integrity sha512-5ti7g45F2KjDJS0DbgnOGI1GyKxGpn4XsKTYJFJrSAWj6VpuvPy/DINRrXNuRVo09VPEkqA+IW7QwaG9icptQg== dependencies: - "@angular-devkit/core" "16.1.8" + "@angular-devkit/core" "17.0.9" jsonc-parser "3.2.0" - magic-string "0.30.0" - ora "5.4.1" - rxjs "7.8.1" - -"@angular-devkit/schematics@16.2.8": - version "16.2.8" - resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-16.2.8.tgz#cc11cf6d00cd9131adbede9a99f3a617aedd5bc4" - integrity sha512-MBiKZOlR9/YMdflALr7/7w/BGAfo/BGTrlkqsIB6rDWV1dYiCgxI+033HsiNssLS6RQyCFx/e7JA2aBBzu9zEg== - dependencies: - "@angular-devkit/core" "16.2.8" - jsonc-parser "3.2.0" - magic-string "0.30.1" + magic-string "0.30.5" ora "5.4.1" rxjs "7.8.1" @@ -1688,7 +1666,7 @@ "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.13", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15": +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15": version "1.4.15" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== @@ -1716,6 +1694,13 @@ dependencies: lodash "^4.17.21" +"@ljharb/through@^2.3.9": + version "2.3.11" + resolved "https://registry.yarnpkg.com/@ljharb/through/-/through-2.3.11.tgz#783600ff12c06f21a76cc26e33abd0b1595092f9" + integrity sha512-ccfcIDlogiXNq5KcbAwbaO7lMh3Tm1i3khMPYpxlK8hH/W53zN81KM9coerRLOnTGu3nfXIniAmQbRI9OxbC0w== + dependencies: + call-bind "^1.0.2" + "@lukeed/csprng@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@lukeed/csprng/-/csprng-1.1.0.tgz#1e3e4bd05c1cc7a0b2ddbd8a03f39f6e4b5e6cfe" @@ -1750,14 +1735,14 @@ dependencies: "@monaco-editor/loader" "^1.4.0" -"@nestjs/cli@^10.2.1": - version "10.2.1" - resolved "https://registry.yarnpkg.com/@nestjs/cli/-/cli-10.2.1.tgz#a1d32c28e188f0fb4c3f54235c55745de4c6dd7f" - integrity sha512-CAJAQwmxFZfB3RTvqz/eaXXWpyU+mZ4QSqfBYzjneTsPgF+uyOAW3yQpaLNn9Dfcv39R9UxSuAhayv6yuFd+Jg== +"@nestjs/cli@^10.3.0": + version "10.3.0" + resolved "https://registry.yarnpkg.com/@nestjs/cli/-/cli-10.3.0.tgz#5f9ef49a60baf4b39cb87e4b74240f7c9339e923" + integrity sha512-37h+wSDItY0NE/x3a/M9yb2cXzfsD4qoE26rHgFn592XXLelDN12wdnfn7dTIaiRZT7WOCdQ+BYP9mQikR4AsA== dependencies: - "@angular-devkit/core" "16.2.8" - "@angular-devkit/schematics" "16.2.8" - "@angular-devkit/schematics-cli" "16.2.8" + "@angular-devkit/core" "17.0.9" + "@angular-devkit/schematics" "17.0.9" + "@angular-devkit/schematics-cli" "17.0.9" "@nestjs/schematics" "^10.0.1" chalk "4.1.2" chokidar "3.5.3" @@ -1768,21 +1753,20 @@ inquirer "8.2.6" node-emoji "1.11.0" ora "5.4.1" - os-name "4.0.1" rimraf "4.4.1" shelljs "0.8.5" source-map-support "0.5.21" tree-kill "1.2.2" tsconfig-paths "4.2.0" tsconfig-paths-webpack-plugin "4.1.0" - typescript "5.2.2" + typescript "5.3.3" webpack "5.89.0" webpack-node-externals "3.0.0" -"@nestjs/common@^10.2.7": - version "10.2.7" - resolved "https://registry.yarnpkg.com/@nestjs/common/-/common-10.2.7.tgz#339db2efa33d3822dd81d2993bd44b538a7451b6" - integrity sha512-cUtCRXiUstDmh4bSBhVbq4cI439Gngp4LgLGLBmd5dqFQodfXKnSD441ldYfFiLz4rbUsnoMJz/8ZjuIEI+B7A== +"@nestjs/common@^10.3.0": + version "10.3.0" + resolved "https://registry.yarnpkg.com/@nestjs/common/-/common-10.3.0.tgz#d78f0ff2062d1d53c79c170a79c12a1548e2e598" + integrity sha512-DGv34UHsZBxCM3H5QGE2XE/+oLJzz5+714JQjBhjD9VccFlQs3LRxo/epso4l7nJIiNlZkPyIUC8WzfU/5RTsQ== dependencies: uid "2.0.2" iterare "1.2.1" @@ -1819,24 +1803,13 @@ cron "3.1.3" uuid "9.0.1" -"@nestjs/schematics@^10.0.1": - version "10.0.2" - resolved "https://registry.yarnpkg.com/@nestjs/schematics/-/schematics-10.0.2.tgz#d782ac1a6e9372d2f7ede1e2077c3a7484714923" - integrity sha512-DaZZjymYoIfRqC5W62lnYXIIods1PDY6CGc8+IpRwyinzffjKxZ3DF3exu+mdyvllzkXo9DTXkoX4zOPSJHCkw== - dependencies: - "@angular-devkit/core" "16.1.8" - "@angular-devkit/schematics" "16.1.8" - comment-json "4.2.3" - jsonc-parser "3.2.0" - pluralize "8.0.0" - -"@nestjs/schematics@^10.0.3": - version "10.0.3" - resolved "https://registry.yarnpkg.com/@nestjs/schematics/-/schematics-10.0.3.tgz#0f48af0a20983ffecabcd8763213a3e53d43f270" - integrity sha512-2BRujK0GqGQ7j1Zpz+obVfskDnnOeVKt5aXoSaVngKo8Oczy8uYCY+R547TQB+Kf35epdfFER2pVnQrX3/It5A== +"@nestjs/schematics@^10.0.1", "@nestjs/schematics@^10.1.0": + version "10.1.0" + resolved "https://registry.yarnpkg.com/@nestjs/schematics/-/schematics-10.1.0.tgz#bf9be846bafad0f45f0ea5ed80aaaf971bb90873" + integrity sha512-HQWvD3F7O0Sv3qHS2jineWxPLmBTLlyjT6VdSw2EAIXulitmV+ErxB3TCVQQORlNkl5p5cwRYWyBaOblDbNFIQ== dependencies: - "@angular-devkit/core" "16.2.8" - "@angular-devkit/schematics" "16.2.8" + "@angular-devkit/core" "17.0.9" + "@angular-devkit/schematics" "17.0.9" comment-json "4.2.3" jsonc-parser "3.2.0" pluralize "8.0.0" @@ -2228,17 +2201,10 @@ resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== -"@pkgr/utils@^2.3.1": - version "2.4.2" - resolved "https://registry.yarnpkg.com/@pkgr/utils/-/utils-2.4.2.tgz#9e638bbe9a6a6f165580dc943f138fd3309a2cbc" - integrity sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw== - dependencies: - cross-spawn "^7.0.3" - fast-glob "^3.3.0" - is-glob "^4.0.3" - open "^9.1.0" - picocolors "^1.0.0" - tslib "^2.6.0" +"@pkgr/core@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.0.tgz#7d8dacb7fdef0e4387caf7396cbd77f179867d06" + integrity sha512-Zwq5OCzuwJC2jwqmpEQt7Ds1DTi6BWSwoGkbb1n9pO3hzb35BoJELx7c0T23iDkBGkh2e7tvOtjF3tr3OaQHDQ== "@pnpm/config.env-replace@^1.1.0": version "1.1.0" @@ -2821,6 +2787,11 @@ resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.3.tgz#291c243e4b94dbfbc0c0ee26b7666f1d5c030e2c" integrity sha512-ehPtgRgaULsFG8x0NeYJvmyH1hmlfsNLujHe9dQEia/7MAJYdzMSi19JtchUHjmBA6XC/75dK55mzZH+RyieSg== +"@types/normalize-package-data@^2.4.3": + version "2.4.4" + resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz#56e2cc26c397c038fab0e3a917a12d5c5909e901" + integrity sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA== + "@types/parse-json@^4.0.0": version "4.0.2" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239" @@ -3353,7 +3324,7 @@ ansi-colors@4.1.3: resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== -ansi-escapes@^4.2.1: +ansi-escapes@^4.2.1, ansi-escapes@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== @@ -3867,11 +3838,6 @@ before-after-hook@^2.2.0: resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.3.tgz#c51e809c81a4e354084422b9b26bad88249c517c" integrity sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ== -big-integer@^1.6.44: - version "1.6.51" - resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" - integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== - bin-links@^4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/bin-links/-/bin-links-4.0.3.tgz#9e4a3c5900830aee3d7f52178b65e01dcdde64a5" @@ -3957,13 +3923,6 @@ bowser@^2.11.0: resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.11.0.tgz#5ca3c35757a7aa5771500c70a73a9f91ef420a8f" integrity sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA== -bplist-parser@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/bplist-parser/-/bplist-parser-0.2.0.tgz#43a9d183e5bf9d545200ceac3e712f79ebbe8d0e" - integrity sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw== - dependencies: - big-integer "^1.6.44" - brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -4043,13 +4002,6 @@ builtins@^5.0.0: dependencies: semver "^7.0.0" -bundle-name@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bundle-name/-/bundle-name-3.0.0.tgz#ba59bcc9ac785fb67ccdbf104a2bf60c099f0e1a" - integrity sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw== - dependencies: - run-applescript "^5.0.0" - busboy@1.6.0, busboy@^1.0.0: version "1.6.0" resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" @@ -4343,6 +4295,11 @@ cli-width@^3.0.0: resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== +cli-width@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-4.1.0.tgz#42daac41d3c254ef38ad8ac037672130173691c5" + integrity sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ== + client-only@0.0.1, client-only@^0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1" @@ -4655,6 +4612,16 @@ cosmiconfig@^8.0.0, cosmiconfig@^8.1.3, cosmiconfig@^8.2.0: parse-json "^5.2.0" path-type "^4.0.0" +cosmiconfig@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-9.0.0.tgz#34c3fc58287b915f3ae905ab6dc3de258b55ad9d" + integrity sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg== + dependencies: + env-paths "^2.2.1" + import-fresh "^3.3.0" + js-yaml "^4.1.0" + parse-json "^5.2.0" + create-jest@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" @@ -4773,12 +4740,10 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" -date-fns@^2.29.3: - version "2.30.0" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0" - integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw== - dependencies: - "@babel/runtime" "^7.21.0" +dayjs@^1.11.9: + version "1.11.10" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.10.tgz#68acea85317a6e164457d6d6947564029a6a16a0" + integrity sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ== debug@2.6.9: version "2.6.9" @@ -4821,24 +4786,6 @@ deepmerge@^4.2.2, deepmerge@^4.3.1: resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== -default-browser-id@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/default-browser-id/-/default-browser-id-3.0.0.tgz#bee7bbbef1f4e75d31f98f4d3f1556a14cea790c" - integrity sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA== - dependencies: - bplist-parser "^0.2.0" - untildify "^4.0.0" - -default-browser@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/default-browser/-/default-browser-4.0.0.tgz#53c9894f8810bf86696de117a6ce9085a3cbc7da" - integrity sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA== - dependencies: - bundle-name "^3.0.0" - default-browser-id "^3.0.0" - execa "^7.1.1" - titleize "^3.0.0" - defaults@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.4.tgz#b0b02062c1e2aa62ff5d9528f0f98baa90978d7a" @@ -4855,11 +4802,6 @@ define-data-property@^1.0.1, define-data-property@^1.1.1: gopd "^1.0.1" has-property-descriptors "^1.0.0" -define-lazy-prop@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz#dbb19adfb746d7fc6d734a06b72f4a00d021255f" - integrity sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg== - define-properties@^1.1.3, define-properties@^1.1.4, define-properties@^1.2.0, define-properties@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" @@ -5090,13 +5032,6 @@ encoding@^0.1.12, encoding@^0.1.13: dependencies: iconv-lite "^0.6.2" -end-of-stream@^1.1.0: - version "1.4.4" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" - integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== - dependencies: - once "^1.4.0" - enhanced-resolve@^5.0.0, enhanced-resolve@^5.12.0, enhanced-resolve@^5.15.0, enhanced-resolve@^5.7.0: version "5.15.0" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35" @@ -5115,15 +5050,15 @@ entities@~2.1.0: resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5" integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w== -env-ci@^10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/env-ci/-/env-ci-10.0.0.tgz#38f0c33a0d14df1303cba708339fb5e38535b00c" - integrity sha512-U4xcd/utDYFgMh0yWj07R1H6L5fwhVbmxBCpnL0DbVSDZVnsC82HONw0wxtxNkIAcua3KtbomQvIk5xFZGAQJw== +env-ci@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/env-ci/-/env-ci-11.0.0.tgz#0cbc2c55feb071a3651aaa9fa181a817e696595f" + integrity sha512-apikxMgkipkgTvMdRT9MNqWx5VLOci79F4VBd7Op/7OPjjoanjdAvn6fglMCCEf/1bAh8eOiuEVCUs4V3qP3nQ== dependencies: execa "^8.0.0" java-properties "^1.0.2" -env-paths@^2.2.0: +env-paths@^2.2.0, env-paths@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== @@ -5359,13 +5294,13 @@ eslint-plugin-jsx-a11y@^6.7.1: object.fromentries "^2.0.6" semver "^6.3.0" -eslint-plugin-prettier@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.1.tgz#a3b399f04378f79f066379f544e42d6b73f11515" - integrity sha512-m3u5RnR56asrwV/lDC4GHorlW75DsFfmUcjfCYylTUs85dBRnB7VM6xG8eCMJdeDRnppzmxZVf1GEPJvl1JmNg== +eslint-plugin-prettier@^5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz#17cfade9e732cef32b5f5be53bd4e07afd8e67e1" + integrity sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw== dependencies: prettier-linter-helpers "^1.0.0" - synckit "^0.8.5" + synckit "^0.8.6" "eslint-plugin-react-hooks@^4.5.0 || 5.0.0-canary-7118f5dd7-20230705": version "4.6.0" @@ -5522,21 +5457,6 @@ events@^3.2.0, events@^3.3.0: resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== -execa@^4.0.2: - version "4.1.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" - integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== - dependencies: - cross-spawn "^7.0.0" - get-stream "^5.0.0" - human-signals "^1.1.1" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.0" - onetime "^5.1.0" - signal-exit "^3.0.2" - strip-final-newline "^2.0.0" - execa@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" @@ -5552,21 +5472,6 @@ execa@^5.0.0: signal-exit "^3.0.3" strip-final-newline "^2.0.0" -execa@^7.1.1: - version "7.2.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-7.2.0.tgz#657e75ba984f42a70f38928cedc87d6f2d4fe4e9" - integrity sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA== - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.1" - human-signals "^4.3.0" - is-stream "^3.0.0" - merge-stream "^2.0.0" - npm-run-path "^5.1.0" - onetime "^6.0.0" - signal-exit "^3.0.7" - strip-final-newline "^3.0.0" - execa@^8.0.0: version "8.0.1" resolved "https://registry.yarnpkg.com/execa/-/execa-8.0.1.tgz#51f6a5943b580f963c3ca9c6321796db8cc39b8c" @@ -5645,7 +5550,7 @@ extend@~3.0.2: resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== -external-editor@^3.0.3: +external-editor@^3.0.3, external-editor@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== @@ -5741,6 +5646,13 @@ figures@^5.0.0: escape-string-regexp "^5.0.0" is-unicode-supported "^1.2.0" +figures@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/figures/-/figures-6.0.1.tgz#75a2baf3ca8c63e2ea253179e9f1157833587ea4" + integrity sha512-0oY/olScYD4IhQ8u//gCPA4F3mlTn2dacYmiDm/mbDQvpmLjV4uH+zhsQ5IyXRyvqkvtUkXkNdGvg5OFJTCsuQ== + dependencies: + is-unicode-supported "^2.0.0" + file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -5773,6 +5685,11 @@ find-root@^1.1.0: resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== +find-up-simple@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/find-up-simple/-/find-up-simple-1.0.0.tgz#21d035fde9fdbd56c8f4d2f63f32fd93a1cfc368" + integrity sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw== + find-up@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" @@ -6064,14 +5981,7 @@ get-package-type@^0.1.0: resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== -get-stream@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" - integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== - dependencies: - pump "^3.0.0" - -get-stream@^6.0.0, get-stream@^6.0.1: +get-stream@^6.0.0: version "6.0.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== @@ -6186,17 +6096,6 @@ glob@^7.0.0, glob@^7.1.3, glob@^7.1.4, glob@^7.2.0: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" - integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^5.0.1" - once "^1.3.0" - glob@^9.2.0: version "9.3.5" resolved "https://registry.yarnpkg.com/glob/-/glob-9.3.5.tgz#ca2ed8ca452781a3009685607fdf025a899dfe21" @@ -6518,21 +6417,11 @@ https-proxy-agent@^7.0.0, https-proxy-agent@^7.0.1: agent-base "^7.0.2" debug "4" -human-signals@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" - integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== - human-signals@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== -human-signals@^4.3.0: - version "4.3.1" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-4.3.1.tgz#ab7f811e851fca97ffbd2c1fe9a958964de321b2" - integrity sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ== - human-signals@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28" @@ -6584,7 +6473,7 @@ import-fresh@^3.1.0, import-fresh@^3.2.1, import-fresh@^3.3.0: parent-module "^1.0.0" resolve-from "^4.0.0" -import-from-esm@^1.0.3: +import-from-esm@^1.0.3, import-from-esm@^1.3.1: version "1.3.3" resolved "https://registry.yarnpkg.com/import-from-esm/-/import-from-esm-1.3.3.tgz#eea1c4ad86a54bf425b3b71fca56d50215ccc6b7" integrity sha512-U3Qt/CyfFpTUv6LOP2jRTLYjphH6zg3okMfHbyqRa/W2w6hr8OsJWVggNlR4jxuojQy81TgTJTxgSkyoteRGMQ== @@ -6625,6 +6514,11 @@ indent-string@^5.0.0: resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-5.0.0.tgz#4fd2980fccaf8622d14c64d694f4cf33c81951a5" integrity sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg== +index-to-position@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/index-to-position/-/index-to-position-0.1.2.tgz#e11bfe995ca4d8eddb1ec43274488f3c201a7f09" + integrity sha512-MWDKS3AS1bGCHLBA2VLImJz42f7bJh8wQsTGCzI3j519/CASStoDONUBVz2I/VID0MpiX3SGSnbOD2xUalbE5g== + infer-owner@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" @@ -6671,27 +6565,6 @@ init-package-json@^6.0.0: validate-npm-package-license "^3.0.4" validate-npm-package-name "^5.0.0" -inquirer@8.2.4: - version "8.2.4" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.2.4.tgz#ddbfe86ca2f67649a67daa6f1051c128f684f0b4" - integrity sha512-nn4F01dxU8VeKfq192IjLsxu0/OmMZ4Lg3xKAns148rCaXP6ntAoEkVYZThWjwON8AlzdZZi6oqnhNbxUG9hVg== - dependencies: - ansi-escapes "^4.2.1" - chalk "^4.1.1" - cli-cursor "^3.1.0" - cli-width "^3.0.0" - external-editor "^3.0.3" - figures "^3.0.0" - lodash "^4.17.21" - mute-stream "0.0.8" - ora "^5.4.1" - run-async "^2.4.0" - rxjs "^7.5.5" - string-width "^4.1.0" - strip-ansi "^6.0.0" - through "^2.3.6" - wrap-ansi "^7.0.0" - inquirer@8.2.6: version "8.2.6" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.2.6.tgz#733b74888195d8d400a67ac332011b5fae5ea562" @@ -6713,6 +6586,27 @@ inquirer@8.2.6: through "^2.3.6" wrap-ansi "^6.0.1" +inquirer@9.2.11: + version "9.2.11" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-9.2.11.tgz#e9003755c233a414fceda1891c23bd622cad4a95" + integrity sha512-B2LafrnnhbRzCWfAdOXisUzL89Kg8cVJlYmhqoi3flSiV/TveO+nsXwgKr9h9PIo+J1hz7nBSk6gegRIMBBf7g== + dependencies: + "@ljharb/through" "^2.3.9" + ansi-escapes "^4.3.2" + chalk "^5.3.0" + cli-cursor "^3.1.0" + cli-width "^4.1.0" + external-editor "^3.1.0" + figures "^5.0.0" + lodash "^4.17.21" + mute-stream "1.0.0" + ora "^5.4.1" + run-async "^3.0.0" + rxjs "^7.8.1" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wrap-ansi "^6.2.0" + internal-slot@^1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.6.tgz#37e756098c4911c5e912b8edbf71ed3aa116f930" @@ -6827,16 +6721,6 @@ is-date-object@^1.0.1, is-date-object@^1.0.5: dependencies: has-tostringtag "^1.0.0" -is-docker@^2.0.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" - integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== - -is-docker@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-3.0.0.tgz#90093aa3106277d8a77a5910dbae71747e15a200" - integrity sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ== - is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -6873,13 +6757,6 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" -is-inside-container@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-inside-container/-/is-inside-container-1.0.0.tgz#e81fba699662eb31dbdaf26766a61d4814717ea4" - integrity sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA== - dependencies: - is-docker "^3.0.0" - is-interactive@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" @@ -7000,6 +6877,11 @@ is-unicode-supported@^1.2.0: resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz#d824984b616c292a2e198207d4a609983842f714" integrity sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ== +is-unicode-supported@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-2.0.0.tgz#fdf32df9ae98ff6ab2cedc155a5a6e895701c451" + integrity sha512-FRdAyx5lusK1iHG0TWpVtk9+1i+GjrzRffhDg4ovQ7mcidMQ6mj+MhKPmvh7Xwyv5gIS06ns49CA7Sqg7lC22Q== + is-weakmap@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2" @@ -7020,13 +6902,6 @@ is-weakset@^2.0.1: call-bind "^1.0.2" get-intrinsic "^1.1.1" -is-wsl@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" - integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== - dependencies: - is-docker "^2.0.0" - isarray@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" @@ -8052,22 +7927,10 @@ luxon@~3.4.0: resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.4.4.tgz#cf20dc27dc532ba41a169c43fdcc0063601577af" integrity sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA== -macos-release@^2.5.0: - version "2.5.1" - resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.5.1.tgz#bccac4a8f7b93163a8d163b8ebf385b3c5f55bf9" - integrity sha512-DXqXhEM7gW59OjZO8NIjBCz9AQ1BEMrfiOAl4AYByHCtVHRF4KoGNO8mqQeM8lRCtQe/UnJ4imO/d2HdkKsd+A== - -magic-string@0.30.0: - version "0.30.0" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.0.tgz#fd58a4748c5c4547338a424e90fa5dd17f4de529" - integrity sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ== - dependencies: - "@jridgewell/sourcemap-codec" "^1.4.13" - -magic-string@0.30.1: - version "0.30.1" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.1.tgz#ce5cd4b0a81a5d032bd69aab4522299b2166284d" - integrity sha512-mbVKXPmS0z0G4XqFDCTllmDQ6coZzn94aMlb0o/A4HEHJCKcanlDZwYJgwnkmgD3jyWhUgj9VsPrfd972yPffA== +magic-string@0.30.5: + version "0.30.5" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.5.tgz#1994d980bd1c8835dc6e78db7cbd4ae4f24746f9" + integrity sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA== dependencies: "@jridgewell/sourcemap-codec" "^1.4.15" @@ -8193,16 +8056,16 @@ marked-terminal@^6.0.0: node-emoji "^2.1.0" supports-hyperlinks "^3.0.0" +marked@^11.0.0: + version "11.1.1" + resolved "https://registry.yarnpkg.com/marked/-/marked-11.1.1.tgz#e1b2407241f744fb1935fac224680874d9aff7a3" + integrity sha512-EgxRjgK9axsQuUa/oKMx5DEY8oXpKJfk61rT5iY3aRlgU6QJtUcxU5OAymdhCvWvhYcd9FKmO5eQoX8m9VGJXg== + marked@^4.0.10: version "4.3.0" resolved "https://registry.yarnpkg.com/marked/-/marked-4.3.0.tgz#796362821b019f734054582038b116481b456cf3" integrity sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A== -marked@^9.0.0: - version "9.1.2" - resolved "https://registry.yarnpkg.com/marked/-/marked-9.1.2.tgz#a54ca772d2b5a43de7d8ed40111354b4b7985527" - integrity sha512-qoKMJqK0w6vkLk8+KnKZAH6neUZSNaQqVZ/h2yZ9S7CbLuFHyS2viB0jnqcWF9UKjwsAbMrQtnQhdmdvOVOw9w== - mdn-data@2.0.28: version "2.0.28" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.28.tgz#5ec48e7bef120654539069e1ae4ddc81ca490eba" @@ -8322,13 +8185,6 @@ minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" -minimatch@^5.0.1: - version "5.1.6" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" - integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== - dependencies: - brace-expansion "^2.0.1" - minimatch@^8.0.2: version "8.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-8.0.4.tgz#847c1b25c014d4e9a7f68aaf63dedd668a626229" @@ -8491,7 +8347,7 @@ mute-stream@0.0.8: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== -mute-stream@~1.0.0: +mute-stream@1.0.0, mute-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-1.0.0.tgz#e31bd9fe62f0aed23520aa4324ea6671531e013e" integrity sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA== @@ -8505,10 +8361,10 @@ mz@^2.4.0, mz@^2.7.0: object-assign "^4.0.1" thenify-all "^1.0.0" -nanoid@^3.3.4, nanoid@^3.3.6: - version "3.3.6" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" - integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== +nanoid@^3.3.4, nanoid@^3.3.6, nanoid@^3.3.7: + version "3.3.7" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" + integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== natural-compare-lite@^1.4.0: version "1.4.0" @@ -8768,7 +8624,7 @@ npm-registry-fetch@^16.0.0, npm-registry-fetch@^16.1.0: npm-package-arg "^11.0.0" proc-log "^3.0.0" -npm-run-path@^4.0.0, npm-run-path@^4.0.1: +npm-run-path@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== @@ -8988,7 +8844,7 @@ on-finished@2.4.1: dependencies: ee-first "1.1.1" -once@^1.3.0, once@^1.3.1, once@^1.4.0: +once@^1.3.0, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== @@ -9009,16 +8865,6 @@ onetime@^6.0.0: dependencies: mimic-fn "^4.0.0" -open@^9.1.0: - version "9.1.0" - resolved "https://registry.yarnpkg.com/open/-/open-9.1.0.tgz#684934359c90ad25742f5a26151970ff8c6c80b6" - integrity sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg== - dependencies: - default-browser "^4.0.0" - define-lazy-prop "^3.0.0" - is-inside-container "^1.0.0" - is-wsl "^2.2.0" - opener@^1.5.1: version "1.5.2" resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" @@ -9051,14 +8897,6 @@ ora@5.4.1, ora@^5.4.1: strip-ansi "^6.0.0" wcwidth "^1.0.1" -os-name@4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/os-name/-/os-name-4.0.1.tgz#32cee7823de85a8897647ba4d76db46bf845e555" - integrity sha512-xl9MAoU97MH1Xt5K9ERft2YfCAoaO6msy1OBA0ozxEC0x0TmIoE6K3QvgJMMZA9yKGLmHXNY/YZoDbiGDj4zYw== - dependencies: - macos-release "^2.5.0" - windows-release "^4.0.0" - os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" @@ -9248,6 +9086,15 @@ parse-json@^7.0.0: lines-and-columns "^2.0.3" type-fest "^3.8.0" +parse-json@^8.0.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-8.1.0.tgz#91cdc7728004e955af9cb734de5684733b24a717" + integrity sha512-rum1bPifK5SSar35Z6EKZuYPJx85pkNaFrxBK3mwdfSJ1/WKbYrjoW/zTPSjRRamfmVX1ACBIdFAO0VRErW/EA== + dependencies: + "@babel/code-frame" "^7.22.13" + index-to-position "^0.1.2" + type-fest "^4.7.1" + parse5-htmlparser2-tree-adapter@^6.0.0: version "6.0.1" resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6" @@ -9354,7 +9201,12 @@ picocolors@^1.0.0: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== -picomatch@2.3.1, picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: +picomatch@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-3.0.1.tgz#817033161def55ec9638567a2f3bbc876b3e7516" + integrity sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== @@ -9486,7 +9338,7 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.2.0: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@8.4.31, postcss@^8.4.23, postcss@^8.4.31: +postcss@8.4.31: version "8.4.31" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d" integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ== @@ -9495,6 +9347,15 @@ postcss@8.4.31, postcss@^8.4.23, postcss@^8.4.31: picocolors "^1.0.0" source-map-js "^1.0.2" +postcss@^8.4.23, postcss@^8.4.33: + version "8.4.33" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.33.tgz#1378e859c9f69bf6f638b990a0212f43e2aaa742" + integrity sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg== + dependencies: + nanoid "^3.3.7" + picocolors "^1.0.0" + source-map-js "^1.0.2" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -9611,14 +9472,6 @@ psl@^1.1.28: resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== -pump@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" - integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - punycode@^2.1.0, punycode@^2.1.1: version "2.3.0" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" @@ -9797,6 +9650,15 @@ read-pkg-up@^10.0.0: read-pkg "^8.1.0" type-fest "^4.2.0" +read-pkg-up@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-11.0.0.tgz#8916ffc6af2a7538b43bcc2c6445d4450ffe5a74" + integrity sha512-LOVbvF1Q0SZdjClSefZ0Nz5z8u+tIE7mV5NibzmE9VYmDe9CaBbAVtz1veOSZbofrdsilxuDAYnFenukZVp8/Q== + dependencies: + find-up-simple "^1.0.0" + read-pkg "^9.0.0" + type-fest "^4.6.0" + read-pkg@^8.0.0, read-pkg@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-8.1.0.tgz#6cf560b91d90df68bce658527e7e3eee75f7c4c7" @@ -9807,6 +9669,17 @@ read-pkg@^8.0.0, read-pkg@^8.1.0: parse-json "^7.0.0" type-fest "^4.2.0" +read-pkg@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-9.0.1.tgz#b1b81fb15104f5dbb121b6bbdee9bbc9739f569b" + integrity sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA== + dependencies: + "@types/normalize-package-data" "^2.4.3" + normalize-package-data "^6.0.0" + parse-json "^8.0.0" + type-fest "^4.6.0" + unicorn-magic "^0.1.0" + read@^2.0.0, read@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/read/-/read-2.1.0.tgz#69409372c54fe3381092bc363a00650b6ac37218" @@ -10103,18 +9976,16 @@ rimraf@^5.0.5: dependencies: glob "^10.3.7" -run-applescript@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/run-applescript/-/run-applescript-5.0.0.tgz#e11e1c932e055d5c6b40d98374e0268d9b11899c" - integrity sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg== - dependencies: - execa "^5.0.0" - run-async@^2.4.0: version "2.4.1" resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== +run-async@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-3.0.0.tgz#42a432f6d76c689522058984384df28be379daad" + integrity sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q== + run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" @@ -10199,10 +10070,10 @@ secure-compare@3.0.1: resolved "https://registry.yarnpkg.com/secure-compare/-/secure-compare-3.0.1.tgz#f1a0329b308b221fae37b9974f3d578d0ca999e3" integrity sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw== -semantic-release@^22.0.5: - version "22.0.5" - resolved "https://registry.yarnpkg.com/semantic-release/-/semantic-release-22.0.5.tgz#8a32168dea44a3275c724133f2f8922b1fbf9aac" - integrity sha512-ESCEQsZlBj1DWMA84RthaJzQHHnihoGk49s9nUxHfRNUNZelLE9JZrE94bHO2Y00EWb7iwrzr1OYhv5QNVmf8A== +semantic-release@^23.0.0: + version "23.0.0" + resolved "https://registry.yarnpkg.com/semantic-release/-/semantic-release-23.0.0.tgz#ecb3685116f5ff284824e7c377cf9a3ce80c5428" + integrity sha512-Jz7jEWO2igTtske112gC4PPE2whCMVrsgxUPG3/SZI7VE357suIUZFlJd1Yu0g2I6RPc2HxNEfUg7KhmDTjwqg== dependencies: "@semantic-release/commit-analyzer" "^11.0.0" "@semantic-release/error" "^4.0.0" @@ -10210,23 +10081,24 @@ semantic-release@^22.0.5: "@semantic-release/npm" "^11.0.0" "@semantic-release/release-notes-generator" "^12.0.0" aggregate-error "^5.0.0" - cosmiconfig "^8.0.0" + cosmiconfig "^9.0.0" debug "^4.0.0" - env-ci "^10.0.0" + env-ci "^11.0.0" execa "^8.0.0" - figures "^5.0.0" + figures "^6.0.0" find-versions "^5.1.0" get-stream "^6.0.0" git-log-parser "^1.2.0" hook-std "^3.0.0" hosted-git-info "^7.0.0" + import-from-esm "^1.3.1" lodash-es "^4.17.21" - marked "^9.0.0" + marked "^11.0.0" marked-terminal "^6.0.0" micromatch "^4.0.2" p-each-series "^3.0.0" p-reduce "^3.0.0" - read-pkg-up "^10.0.0" + read-pkg-up "^11.0.0" resolve-from "^5.0.0" semver "^7.3.2" semver-diff "^4.0.0" @@ -10879,13 +10751,13 @@ symbol-observable@4.0.0: resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-4.0.0.tgz#5b425f192279e87f2f9b937ac8540d1984b39205" integrity sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ== -synckit@^0.8.5: - version "0.8.5" - resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.8.5.tgz#b7f4358f9bb559437f9f167eb6bc46b3c9818fa3" - integrity sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q== +synckit@^0.8.6: + version "0.8.8" + resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.8.8.tgz#fe7fe446518e3d3d49f5e429f443cf08b6edfcd7" + integrity sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ== dependencies: - "@pkgr/utils" "^2.3.1" - tslib "^2.5.0" + "@pkgr/core" "^0.1.0" + tslib "^2.6.2" tailwindcss@^3.4.1: version "3.4.1" @@ -11019,11 +10891,6 @@ tiny-relative-date@^1.3.0: resolved "https://registry.yarnpkg.com/tiny-relative-date/-/tiny-relative-date-1.3.0.tgz#fa08aad501ed730f31cc043181d995c39a935e07" integrity sha512-MOQHpzllWxDCHHaDno30hhLfbouoYlOI8YlMNtvKe1zXbjEVhbcEovQxvZrPvtiYW630GQDoMMarCnjfyfHA+A== -titleize@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/titleize/-/titleize-3.0.0.tgz#71c12eb7fdd2558aa8a44b0be83b8a76694acd53" - integrity sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ== - tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -11110,10 +10977,10 @@ ts-jest@^29.1.1: semver "^7.5.3" yargs-parser "^21.0.1" -ts-loader@^9.5.0: - version "9.5.0" - resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.5.0.tgz#f0a51dda37cc4d8e43e6cb14edebbc599b0c3aa2" - integrity sha512-LLlB/pkB4q9mW2yLdFMnK3dEHbrBjeZTYguaaIfusyojBgAGf5kF+O6KcWqiGzWqHk0LBsoolrp4VftEURhybg== +ts-loader@^9.5.1: + version "9.5.1" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.5.1.tgz#63d5912a86312f1fbe32cef0859fb8b2193d9b89" + integrity sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg== dependencies: chalk "^4.1.0" enhanced-resolve "^5.0.0" @@ -11121,10 +10988,10 @@ ts-loader@^9.5.0: semver "^7.3.4" source-map "^0.7.4" -ts-node@^10.9.1: - version "10.9.1" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" - integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== +ts-node@^10.9.2: + version "10.9.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" + integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== dependencies: "@cspotcode/source-map-support" "^0.8.0" "@tsconfig/node10" "^1.0.7" @@ -11168,7 +11035,7 @@ tsconfig-paths@^3.14.2: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@2.6.2, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.4.0, tslib@^2.5.0, tslib@^2.6.0: +tslib@2.6.2, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.4.0, tslib@^2.5.0, tslib@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== @@ -11248,6 +11115,11 @@ type-fest@^4.2.0: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.5.0.tgz#79208f4dbb8a9946a55889e9f482b95a3292ee41" integrity sha512-diLQivFzddJl4ylL3jxSkEc39Tpw7o1QeEHIPxVwryDK2lpB7Nqhzhuo6v5/Ls08Z0yPSAhsyAWlv1/H0ciNmw== +type-fest@^4.6.0, type-fest@^4.7.1: + version "4.9.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.9.0.tgz#d29c8efe5b1e703feeb29cef23d887b2f479844d" + integrity sha512-KS/6lh/ynPGiHD/LnAobrEFq3Ad4pBzOlJ1wAnJx9N4EYoqFhMfLIBjUT2UEx4wg5ZE+cC1ob6DCSpppVo+rtg== + type-is@^1.6.4, type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" @@ -11300,20 +11172,20 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== -typeorm@^0.3.17: - version "0.3.17" - resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.3.17.tgz#a73c121a52e4fbe419b596b244777be4e4b57949" - integrity sha512-UDjUEwIQalO9tWw9O2A4GU+sT3oyoUXheHJy4ft+RFdnRdQctdQ34L9SqE2p7LdwzafHx1maxT+bqXON+Qnmig== +typeorm@^0.3.19: + version "0.3.19" + resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.3.19.tgz#a985ce8ae36d266018e44fed5e27a4a5da34ad2a" + integrity sha512-OGelrY5qEoAU80mR1iyvmUHiKCPUydL6xp6bebXzS7jyv/X70Gp/jBWRAfF4qGOfy2A7orMiGRfwsBUNbEL65g== dependencies: "@sqltools/formatter" "^1.2.5" app-root-path "^3.1.0" buffer "^6.0.3" chalk "^4.1.2" cli-highlight "^2.1.11" - date-fns "^2.29.3" + dayjs "^1.11.9" debug "^4.3.4" dotenv "^16.0.3" - glob "^8.1.0" + glob "^10.3.10" mkdirp "^2.1.3" reflect-metadata "^0.1.13" sha.js "^2.4.11" @@ -11321,21 +11193,16 @@ typeorm@^0.3.17: uuid "^9.0.0" yargs "^17.6.2" -typescript@5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78" - integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w== +typescript@5.3.3, typescript@^5.3.3: + version "5.3.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37" + integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw== typescript@^4.0: version "4.9.5" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== -typescript@^5.3.3: - version "5.3.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37" - integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw== - uc.micro@^1.0.1, uc.micro@^1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" @@ -11401,6 +11268,11 @@ unicode-property-aliases-ecmascript@^2.0.0: resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd" integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== +unicorn-magic@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/unicorn-magic/-/unicorn-magic-0.1.0.tgz#1bb9a51c823aaf9d73a8bfcd3d1a23dde94b0ce4" + integrity sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ== + union@~0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/union/-/union-0.5.0.tgz#b2c11be84f60538537b846edb9ba266ba0090075" @@ -11458,11 +11330,6 @@ unpipe@1.0.0, unpipe@~1.0.0: resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== -untildify@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" - integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== - update-browserslist-db@^1.0.13: version "1.0.13" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" @@ -11742,13 +11609,6 @@ wide-align@^1.1.2, wide-align@^1.1.5: dependencies: string-width "^1.0.2 || 2 || 3 || 4" -windows-release@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/windows-release/-/windows-release-4.0.0.tgz#4725ec70217d1bf6e02c7772413b29cdde9ec377" - integrity sha512-OxmV4wzDKB1x7AZaZgXMVsdJ1qER1ed83ZrTYd5Bwq2HfJVg3DJS8nqlAG4sMoJ7mu8cuRmLEYyU13BKwctRAg== - dependencies: - execa "^4.0.2" - wordwrap@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" @@ -11763,6 +11623,15 @@ wordwrap@^1.0.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"