From fff389359a18ec28db668474cbea180e25355267 Mon Sep 17 00:00:00 2001 From: simon Date: Wed, 21 Dec 2022 12:06:35 +0700 Subject: [PATCH 01/16] update unstable TA_VERSION --- tubearchivist/config/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tubearchivist/config/settings.py b/tubearchivist/config/settings.py index b8ab1bc..8d14ae4 100644 --- a/tubearchivist/config/settings.py +++ b/tubearchivist/config/settings.py @@ -262,4 +262,4 @@ CORS_ALLOW_HEADERS = list(default_headers) + [ # TA application settings TA_UPSTREAM = "https://github.com/tubearchivist/tubearchivist" -TA_VERSION = "v0.3.0" +TA_VERSION = "v0.3.1-unstable" From 5244cddeb3b929c7c90134d6db72d1da69e338c6 Mon Sep 17 00:00:00 2001 From: simon Date: Wed, 21 Dec 2022 14:24:24 +0700 Subject: [PATCH 02/16] add version_check background task --- tubearchivist/home/apps.py | 6 +++ tubearchivist/home/config.json | 3 +- tubearchivist/home/src/ta/config.py | 65 +++++++++++++++++++++++++++++ tubearchivist/home/tasks.py | 10 ++++- 4 files changed, 81 insertions(+), 3 deletions(-) diff --git a/tubearchivist/home/apps.py b/tubearchivist/home/apps.py index 657c0e2..7a70d6e 100644 --- a/tubearchivist/home/apps.py +++ b/tubearchivist/home/apps.py @@ -8,6 +8,7 @@ from home.src.es.connect import ElasticWrap from home.src.es.index_setup import ElasitIndexWrap from home.src.es.snapshot import ElasticSnapshot from home.src.ta.config import AppConfig as ArchivistConfig +from home.src.ta.config import ReleaseVersion from home.src.ta.helper import clear_dl_cache from home.src.ta.ta_redis import RedisArchivist @@ -34,6 +35,7 @@ class StartupCheck: self.make_folders() clear_dl_cache(self.config_handler.config) self.snapshot_check() + self.ta_version_check() self.set_has_run() def get_has_run(self): @@ -120,6 +122,10 @@ class StartupCheck: print("elasticsearch version check passed") + def ta_version_check(self): + """remove key if updated now""" + ReleaseVersion().is_updated() + class HomeConfig(AppConfig): """call startup funcs""" diff --git a/tubearchivist/home/config.json b/tubearchivist/home/config.json index c8450d0..19539f8 100644 --- a/tubearchivist/home/config.json +++ b/tubearchivist/home/config.json @@ -49,6 +49,7 @@ "check_reindex_days": 90, "thumbnail_check": {"minute": "0", "hour": "17", "day_of_week": "*"}, "run_backup": {"minute": "0", "hour": "8", "day_of_week": "0"}, - "run_backup_rotate": 5 + "run_backup_rotate": 5, + "version_check": {"minute": "0", "hour": "11", "day_of_week": "*"} } } diff --git a/tubearchivist/home/src/ta/config.py b/tubearchivist/home/src/ta/config.py index e6d4eb3..27e0cf1 100644 --- a/tubearchivist/home/src/ta/config.py +++ b/tubearchivist/home/src/ta/config.py @@ -8,7 +8,9 @@ import json import os import re +import requests from celery.schedules import crontab +from django.conf import settings from home.src.ta.ta_redis import RedisArchivist @@ -268,3 +270,66 @@ class ScheduleBuilder: schedule_dict.update(to_add) return schedule_dict + + +class ReleaseVersion: + """compare local version with remote version""" + + REMOTE_URL = "https://www.tubearchivist.com/api/release/latest/" + NEW_KEY = "versioncheck:new" + + def __init__(self): + self.local_version = self._parse_version(settings.TA_VERSION) + self.is_unstable = settings.TA_VERSION.endswith("-unstable") + self.remote_version = False + self.is_breaking = False + self.response = False + + def check(self): + """check version""" + print(f"[{self.local_version}]: look for updates") + self.get_remote_version() + new_version, is_breaking = self._has_update() + if new_version: + message = { + "status": True, + "version": new_version, + "is_breaking": is_breaking, + } + RedisArchivist().set_message(self.NEW_KEY, message) + print(f"[{self.local_version}]: found new version {new_version}") + + def get_remote_version(self): + """read version from remote""" + self.response = requests.get(self.REMOTE_URL, timeout=20).json() + remote_version_str = self.response["release_version"] + self.remote_version = self._parse_version(remote_version_str) + self.is_breaking = self.response["breaking_changes"] + + def _has_update(self): + """check if there is an update""" + for idx, number in enumerate(self.local_version): + is_newer = self.remote_version[idx] > number + if is_newer: + return self.response["release_version"], self.is_breaking + + if self.is_unstable and self.local_version == self.remote_version: + return self.response["release_version"], self.is_breaking + + return False, False + + @staticmethod + def _parse_version(version): + """return version parts""" + clean = version.rstrip("-unstable").lstrip("v") + return tuple((int(i) for i in clean.split("."))) + + def is_updated(self): + """check if update happened in the mean time""" + message = RedisArchivist().get_message(self.NEW_KEY) + if not message.get("status"): + return + + if self._parse_version(message.get("version")) == self.local_version: + print(f"[{self.local_version}]: update completed") + RedisArchivist().del_message(self.NEW_KEY) diff --git a/tubearchivist/home/tasks.py b/tubearchivist/home/tasks.py index ee830e9..f638350 100644 --- a/tubearchivist/home/tasks.py +++ b/tubearchivist/home/tasks.py @@ -22,7 +22,7 @@ from home.src.es.index_setup import ElasitIndexWrap from home.src.index.channel import YoutubeChannel from home.src.index.filesystem import ImportFolderScanner, scan_filesystem from home.src.index.reindex import Reindex, ReindexManual, ReindexOutdated -from home.src.ta.config import AppConfig, ScheduleBuilder +from home.src.ta.config import AppConfig, ReleaseVersion, ScheduleBuilder from home.src.ta.helper import UrlListParser, clear_dl_cache from home.src.ta.ta_redis import RedisArchivist, RedisQueue @@ -290,9 +290,15 @@ def index_channel_playlists(channel_id): channel.index_channel_playlists() +@shared_task(name="version_check") +def version_check(): + """check for new updates""" + ReleaseVersion().check() + + try: app.conf.beat_schedule = ScheduleBuilder().build_schedule() except KeyError: - # update path from v0.0.8 to v0.0.9 to load new defaults + # update path to load new defaults StartupCheck().sync_redis_state() app.conf.beat_schedule = ScheduleBuilder().build_schedule() From ae6bb4e7572d7c67cffb7c13fee5984f422289fa Mon Sep 17 00:00:00 2001 From: simon Date: Wed, 21 Dec 2022 17:44:28 +0700 Subject: [PATCH 03/16] consolidate min var initiate for simple views --- tubearchivist/home/views.py | 103 +++++++++++++++++++----------------- 1 file changed, 53 insertions(+), 50 deletions(-) diff --git a/tubearchivist/home/views.py b/tubearchivist/home/views.py index a305bbe..427a463 100644 --- a/tubearchivist/home/views.py +++ b/tubearchivist/home/views.py @@ -257,6 +257,18 @@ class ArchivistResultsView(ArchivistViewConfig): self.context["pagination"] = self.pagination_handler.pagination +class MinView(View): + """to inherit from for minimal config vars""" + + @staticmethod + def get_min_context(request): + """build minimal vars for context""" + return { + "colors": AppConfig(request.user.id).colors, + "version": settings.TA_VERSION, + } + + class HomeView(ArchivistResultsView): """resolves to / handle home page and video search post functionality @@ -298,20 +310,23 @@ class HomeView(ArchivistResultsView): self.data["query"] = query -class LoginView(View): +class LoginView(MinView): """resolves to /login/ Greeting and login page """ SEC_IN_DAY = 60 * 60 * 24 - @staticmethod - def get(request): + def get(self, request): """handle get requests""" - failed = bool(request.GET.get("failed")) - colors = AppConfig(request.user.id).colors - form = CustomAuthForm() - context = {"colors": colors, "form": form, "form_error": failed} + context = self.get_min_context(request) + context.update( + { + "form": CustomAuthForm(), + "form_error": bool(request.GET.get("failed")), + } + ) + return render(request, "home/login.html", context) def post(self, request): @@ -333,19 +348,15 @@ class LoginView(View): return redirect("/login?failed=true") -class AboutView(View): +class AboutView(MinView): """resolves to /about/ show helpful how to information """ - @staticmethod - def get(request): + def get(self, request): """handle http get""" - context = { - "title": "About", - "colors": AppConfig(request.user.id).colors, - "version": settings.TA_VERSION, - } + context = self.get_min_context(request) + context.update({"title": "About"}) return render(request, "home/about.html", context) @@ -843,7 +854,7 @@ class PlaylistView(ArchivistResultsView): return redirect("playlist") -class VideoView(View): +class VideoView(MinView): """resolves to /video// display details about a single video """ @@ -869,17 +880,18 @@ class VideoView(View): request_type="video", request_id=video_id ).get_progress() - context = { - "video": video_data, - "playlist_nav": playlist_nav, - "title": video_data.get("title"), - "colors": config_handler.colors, - "cast": config_handler.config["application"]["enable_cast"], - "version": settings.TA_VERSION, - "config": config_handler.config, - "position": time_parser(request.GET.get("t")), - "reindex": reindex.get("state"), - } + context = self.get_min_context(request) + context.update( + { + "video": video_data, + "playlist_nav": playlist_nav, + "title": video_data.get("title"), + "cast": config_handler.config["application"]["enable_cast"], + "config": config_handler.config, + "position": time_parser(request.GET.get("t")), + "reindex": reindex.get("state"), + } + ) return render(request, "home/video.html", context) @staticmethod @@ -936,7 +948,7 @@ class SearchView(ArchivistResultsView): return render(request, "home/search.html", self.context) -class SettingsView(View): +class SettingsView(MinView): """resolves to /settings/ handle the settings page, display current settings, take post request from the form to update settings @@ -944,28 +956,19 @@ class SettingsView(View): def get(self, request): """read and display current settings""" - config_handler = AppConfig(request.user.id) - colors = config_handler.colors - - available_backups = ElasticBackup().get_all_backup_files() - user_form = UserSettingsForm() - app_form = ApplicationSettingsForm() - scheduler_form = SchedulerSettingsForm() - snapshots = ElasticSnapshot().get_snapshot_stats() - token = self.get_token(request) - - context = { - "title": "Settings", - "config": config_handler.config, - "api_token": token, - "colors": colors, - "available_backups": available_backups, - "user_form": user_form, - "app_form": app_form, - "scheduler_form": scheduler_form, - "snapshots": snapshots, - "version": settings.TA_VERSION, - } + context = self.get_min_context(request) + context.update( + { + "title": "Settings", + "config": AppConfig(request.user.id).config, + "api_token": self.get_token(request), + "available_backups": ElasticBackup().get_all_backup_files(), + "user_form": UserSettingsForm(), + "app_form": ApplicationSettingsForm(), + "scheduler_form": SchedulerSettingsForm(), + "snapshots": ElasticSnapshot().get_snapshot_stats(), + } + ) return render(request, "home/settings.html", context) From ef44cb852cd80d28d1b9396407f454d8a871f473 Mon Sep 17 00:00:00 2001 From: simon Date: Wed, 21 Dec 2022 18:26:32 +0700 Subject: [PATCH 04/16] add new version notification to UI --- tubearchivist/home/src/ta/config.py | 12 ++++++++++-- tubearchivist/home/templates/home/base.html | 9 ++++++++- tubearchivist/home/views.py | 4 +++- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/tubearchivist/home/src/ta/config.py b/tubearchivist/home/src/ta/config.py index 27e0cf1..64d5d21 100644 --- a/tubearchivist/home/src/ta/config.py +++ b/tubearchivist/home/src/ta/config.py @@ -326,10 +326,18 @@ class ReleaseVersion: def is_updated(self): """check if update happened in the mean time""" - message = RedisArchivist().get_message(self.NEW_KEY) - if not message.get("status"): + message = self.get_update() + if not message: return if self._parse_version(message.get("version")) == self.local_version: print(f"[{self.local_version}]: update completed") RedisArchivist().del_message(self.NEW_KEY) + + def get_update(self): + """return new version dict if available""" + message = RedisArchivist().get_message(self.NEW_KEY) + if not message.get("status"): + return False + + return message diff --git a/tubearchivist/home/templates/home/base.html b/tubearchivist/home/templates/home/base.html index 71b7dce..2adce0d 100644 --- a/tubearchivist/home/templates/home/base.html +++ b/tubearchivist/home/templates/home/base.html @@ -132,7 +132,14 @@ diff --git a/tubearchivist/home/views.py b/tubearchivist/home/views.py index 427a463..bdf9b27 100644 --- a/tubearchivist/home/views.py +++ b/tubearchivist/home/views.py @@ -36,7 +36,7 @@ from home.src.index.channel import YoutubeChannel, channel_overwrites from home.src.index.generic import Pagination from home.src.index.playlist import YoutubePlaylist from home.src.index.reindex import ReindexProgress -from home.src.ta.config import AppConfig, ScheduleBuilder +from home.src.ta.config import AppConfig, ReleaseVersion, ScheduleBuilder from home.src.ta.helper import UrlListParser, time_parser from home.src.ta.ta_redis import RedisArchivist from home.tasks import extrac_dl, index_channel_playlists, subscribe_to @@ -138,6 +138,7 @@ class ArchivistViewConfig(View): "show_ignored_only": self._get_show_ignore_only(), "show_subed_only": self._get_show_subed_only(), "version": settings.TA_VERSION, + "ta_update": ReleaseVersion().get_update(), } @@ -266,6 +267,7 @@ class MinView(View): return { "colors": AppConfig(request.user.id).colors, "version": settings.TA_VERSION, + "ta_update": ReleaseVersion().get_update(), } From 63f35b19fe0023ef39b40dd358245067cf06525c Mon Sep 17 00:00:00 2001 From: simon Date: Thu, 22 Dec 2022 11:17:06 +0700 Subject: [PATCH 05/16] register version_check schedule --- tubearchivist/home/src/ta/config.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tubearchivist/home/src/ta/config.py b/tubearchivist/home/src/ta/config.py index 64d5d21..abd4475 100644 --- a/tubearchivist/home/src/ta/config.py +++ b/tubearchivist/home/src/ta/config.py @@ -156,6 +156,7 @@ class ScheduleBuilder: "check_reindex": "0 12 *", "thumbnail_check": "0 17 *", "run_backup": "0 18 0", + "version_check": "0 11 *", } CONFIG = ["check_reindex_days", "run_backup_rotate"] MSG = "message:setting" From db21ee8bcbd7598ecd9ec45cac1c1aaaf20e45bf Mon Sep 17 00:00:00 2001 From: simon Date: Thu, 22 Dec 2022 19:01:13 +0700 Subject: [PATCH 06/16] [API] add and use DELETE video endpoint --- tubearchivist/api/README.md | 3 ++- tubearchivist/api/views.py | 16 +++++++++++++++- tubearchivist/home/src/frontend/api_calls.py | 8 -------- tubearchivist/home/src/index/video.py | 3 +++ tubearchivist/static/script.js | 4 ++-- 5 files changed, 22 insertions(+), 12 deletions(-) diff --git a/tubearchivist/api/README.md b/tubearchivist/api/README.md index 3b69e76..ad56ba3 100644 --- a/tubearchivist/api/README.md +++ b/tubearchivist/api/README.md @@ -79,7 +79,8 @@ Pass page number as a query parameter: `page=2`. Defaults to *0*, `page=1` is re /api/video/ ## Video Item View -/api/video/\/ +GET: /api/video/\/ +DELETE: /api/video/\/ ## Video Comment View /api/video/\/comment/ diff --git a/tubearchivist/api/views.py b/tubearchivist/api/views.py index df8554c..f9bb4bc 100644 --- a/tubearchivist/api/views.py +++ b/tubearchivist/api/views.py @@ -9,7 +9,7 @@ from home.src.es.snapshot import ElasticSnapshot from home.src.frontend.searching import SearchForm from home.src.index.generic import Pagination from home.src.index.reindex import ReindexProgress -from home.src.index.video import SponsorBlock +from home.src.index.video import SponsorBlock, YoutubeVideo from home.src.ta.config import AppConfig from home.src.ta.helper import UrlListParser from home.src.ta.ta_redis import RedisArchivist, RedisQueue @@ -95,6 +95,20 @@ class VideoApiView(ApiBaseView): self.get_document(video_id) return Response(self.response, status=self.status_code) + def delete(self, request, video_id): + # pylint: disable=unused-argument + """delete single video""" + message = {"video": video_id} + try: + YoutubeVideo(video_id).delete_media_file() + status_code = 200 + message.update({"state": "delete"}) + except FileNotFoundError: + status_code = 404 + message.update({"state": "not found"}) + + return Response(message, status=status_code) + class VideoApiListView(ApiBaseView): """resolves to /api/video/ diff --git a/tubearchivist/home/src/frontend/api_calls.py b/tubearchivist/home/src/frontend/api_calls.py index 6e0bb78..2175b11 100644 --- a/tubearchivist/home/src/frontend/api_calls.py +++ b/tubearchivist/home/src/frontend/api_calls.py @@ -12,7 +12,6 @@ from home.src.download.subscriptions import ( from home.src.frontend.watched import WatchState from home.src.index.channel import YoutubeChannel from home.src.index.playlist import YoutubePlaylist -from home.src.index.video import YoutubeVideo from home.src.ta.helper import UrlListParser from home.src.ta.ta_redis import RedisArchivist, RedisQueue from home.tasks import ( @@ -73,7 +72,6 @@ class PostData: "db-backup": self._db_backup, "db-restore": self._db_restore, "fs-rescan": self._fs_rescan, - "delete-video": self._delete_video, "delete-channel": self._delete_channel, "delete-playlist": self._delete_playlist, "find-playlists": self._find_playlists, @@ -284,12 +282,6 @@ class PostData: rescan_filesystem.delay() return {"success": True} - def _delete_video(self): - """delete media file, metadata and thumb""" - youtube_id = self.exec_val - YoutubeVideo(youtube_id).delete_media_file() - return {"success": True} - def _delete_channel(self): """delete channel and all matching videos""" channel_id = self.exec_val diff --git a/tubearchivist/home/src/index/video.py b/tubearchivist/home/src/index/video.py index 6b8cb93..45ddeb3 100644 --- a/tubearchivist/home/src/index/video.py +++ b/tubearchivist/home/src/index/video.py @@ -292,6 +292,9 @@ class YoutubeVideo(YouTubeItem, YoutubeSubtitle): """delete video file, meta data""" print(f"{self.youtube_id}: delete video") self.get_from_es() + if not self.json_data: + raise FileNotFoundError + video_base = self.app_conf["videos"] media_url = self.json_data.get("media_url") file_path = os.path.join(video_base, media_url) diff --git a/tubearchivist/static/script.js b/tubearchivist/static/script.js index 09a8580..188c660 100644 --- a/tubearchivist/static/script.js +++ b/tubearchivist/static/script.js @@ -334,8 +334,8 @@ function deleteConfirm() { function deleteVideo(button) { let to_delete = button.getAttribute('data-id'); let to_redirect = button.getAttribute('data-redirect'); - let payload = JSON.stringify({ 'delete-video': to_delete }); - sendPost(payload); + let apiEndpoint = '/api/video/' + to_delete + '/'; + apiRequest(apiEndpoint, 'DELETE'); setTimeout(function () { let redirect = '/channel/' + to_redirect; window.location.replace(redirect); From bad4b485738efd50b8f1325e73c91dc7e3ad9d04 Mon Sep 17 00:00:00 2001 From: simon Date: Thu, 22 Dec 2022 19:12:09 +0700 Subject: [PATCH 07/16] switch forget ignored to API call --- tubearchivist/home/src/frontend/api_calls.py | 8 -------- tubearchivist/static/script.js | 4 ++-- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/tubearchivist/home/src/frontend/api_calls.py b/tubearchivist/home/src/frontend/api_calls.py index 2175b11..5943c6c 100644 --- a/tubearchivist/home/src/frontend/api_calls.py +++ b/tubearchivist/home/src/frontend/api_calls.py @@ -64,7 +64,6 @@ class PostData: "show_subed_only": self._show_subed_only, "dlnow": self._dlnow, "show_ignored_only": self._show_ignored_only, - "forgetIgnore": self._forget_ignore, "addSingle": self._add_single, "deleteQueue": self._delete_queue, "manual-import": self._manual_import, @@ -226,13 +225,6 @@ class PostData: RedisArchivist().set_message(key, value) return {"success": True} - def _forget_ignore(self): - """delete from ta_download index""" - video_id = self.exec_val - print(f"{video_id}: forget from download") - PendingInteract(video_id=video_id).delete_item() - return {"success": True} - def _add_single(self): """add single youtube_id to download queue""" video_id = self.exec_val diff --git a/tubearchivist/static/script.js b/tubearchivist/static/script.js index 188c660..31e1d3d 100644 --- a/tubearchivist/static/script.js +++ b/tubearchivist/static/script.js @@ -203,8 +203,8 @@ function downloadNow(button) { function forgetIgnore(button) { let youtube_id = button.getAttribute('data-id'); - let payload = JSON.stringify({ forgetIgnore: youtube_id }); - sendPost(payload); + let apiEndpoint = '/api/download/' + youtube_id + '/'; + apiRequest(apiEndpoint, "DELETE"); document.getElementById('dl-' + youtube_id).remove(); } From 7a9f5e568589b4e7aa8abdc999160cc9bf464acc Mon Sep 17 00:00:00 2001 From: simon Date: Thu, 22 Dec 2022 19:24:48 +0700 Subject: [PATCH 08/16] [API] add and use DELETE channel endpoint --- tubearchivist/api/README.md | 4 +++- tubearchivist/api/views.py | 15 +++++++++++++++ tubearchivist/home/src/frontend/api_calls.py | 8 -------- tubearchivist/home/src/index/channel.py | 3 +++ tubearchivist/static/script.js | 4 ++-- 5 files changed, 23 insertions(+), 11 deletions(-) diff --git a/tubearchivist/api/README.md b/tubearchivist/api/README.md index ad56ba3..e26978b 100644 --- a/tubearchivist/api/README.md +++ b/tubearchivist/api/README.md @@ -165,7 +165,9 @@ POST /api/channel/ ``` ## Channel Item View -/api/channel/\/ +GET: /api/channel/\/ +DELETE: /api/channel/\/ +- Will delete channel with all it's videos ## Channel Videos View /api/channel/\/video/ diff --git a/tubearchivist/api/views.py b/tubearchivist/api/views.py index f9bb4bc..51cf296 100644 --- a/tubearchivist/api/views.py +++ b/tubearchivist/api/views.py @@ -7,6 +7,7 @@ from home.src.download.yt_dlp_base import CookieHandler from home.src.es.connect import ElasticWrap from home.src.es.snapshot import ElasticSnapshot from home.src.frontend.searching import SearchForm +from home.src.index.channel import YoutubeChannel from home.src.index.generic import Pagination from home.src.index.reindex import ReindexProgress from home.src.index.video import SponsorBlock, YoutubeVideo @@ -265,6 +266,20 @@ class ChannelApiView(ApiBaseView): self.get_document(channel_id) return Response(self.response, status=self.status_code) + def delete(self, request, channel_id): + # pylint: disable=unused-argument + """delete channel""" + message = {"channel": channel_id} + try: + YoutubeChannel(channel_id).delete_channel() + status_code = 200 + message.update({"state": "delete"}) + except FileNotFoundError: + status_code = 404 + message.update({"state": "not found"}) + + return Response(message, status=status_code) + class ChannelApiListView(ApiBaseView): """resolves to /api/channel/ diff --git a/tubearchivist/home/src/frontend/api_calls.py b/tubearchivist/home/src/frontend/api_calls.py index 5943c6c..71c5182 100644 --- a/tubearchivist/home/src/frontend/api_calls.py +++ b/tubearchivist/home/src/frontend/api_calls.py @@ -10,7 +10,6 @@ from home.src.download.subscriptions import ( PlaylistSubscription, ) from home.src.frontend.watched import WatchState -from home.src.index.channel import YoutubeChannel from home.src.index.playlist import YoutubePlaylist from home.src.ta.helper import UrlListParser from home.src.ta.ta_redis import RedisArchivist, RedisQueue @@ -71,7 +70,6 @@ class PostData: "db-backup": self._db_backup, "db-restore": self._db_restore, "fs-rescan": self._fs_rescan, - "delete-channel": self._delete_channel, "delete-playlist": self._delete_playlist, "find-playlists": self._find_playlists, } @@ -274,12 +272,6 @@ class PostData: rescan_filesystem.delay() return {"success": True} - def _delete_channel(self): - """delete channel and all matching videos""" - channel_id = self.exec_val - YoutubeChannel(channel_id).delete_channel() - return {"success": True} - def _delete_playlist(self): """delete playlist, only metadata or incl all videos""" playlist_dict = self.exec_val diff --git a/tubearchivist/home/src/index/channel.py b/tubearchivist/home/src/index/channel.py index fb80e25..90e0c11 100644 --- a/tubearchivist/home/src/index/channel.py +++ b/tubearchivist/home/src/index/channel.py @@ -298,6 +298,9 @@ class YoutubeChannel(YouTubeItem): """delete channel and all videos""" print(f"{self.youtube_id}: delete channel") self.get_from_es() + if not self.json_data: + raise FileNotFoundError + folder_path = self.get_folder_path() print(f"{self.youtube_id}: delete all media files") try: diff --git a/tubearchivist/static/script.js b/tubearchivist/static/script.js index 31e1d3d..6b919bf 100644 --- a/tubearchivist/static/script.js +++ b/tubearchivist/static/script.js @@ -344,8 +344,8 @@ function deleteVideo(button) { function deleteChannel(button) { let to_delete = button.getAttribute('data-id'); - let payload = JSON.stringify({ 'delete-channel': to_delete }); - sendPost(payload); + let apiEndpoint = '/api/channel/' + to_delete + '/'; + apiRequest(apiEndpoint, 'DELETE'); setTimeout(function () { window.location.replace('/channel/'); }, 1000); From 8af10f8c071723f0a5390a14a1406bf67f987a9e Mon Sep 17 00:00:00 2001 From: simon Date: Thu, 22 Dec 2022 19:42:59 +0700 Subject: [PATCH 09/16] switch ignore to API call --- tubearchivist/api/views.py | 2 +- tubearchivist/home/src/frontend/api_calls.py | 10 ---------- tubearchivist/static/script.js | 4 ++-- 3 files changed, 3 insertions(+), 13 deletions(-) diff --git a/tubearchivist/api/views.py b/tubearchivist/api/views.py index 51cf296..e24c2ef 100644 --- a/tubearchivist/api/views.py +++ b/tubearchivist/api/views.py @@ -404,7 +404,7 @@ class DownloadApiView(ApiBaseView): def post(self, request, video_id): """post to video to change status""" - item_status = request.data["status"] + item_status = request.data.get("status") if item_status not in self.valid_status: message = f"{video_id}: invalid status {item_status}" print(message) diff --git a/tubearchivist/home/src/frontend/api_calls.py b/tubearchivist/home/src/frontend/api_calls.py index 71c5182..47d68b3 100644 --- a/tubearchivist/home/src/frontend/api_calls.py +++ b/tubearchivist/home/src/frontend/api_calls.py @@ -53,7 +53,6 @@ class PostData: "change_view": self._change_view, "change_grid": self._change_grid, "rescan_pending": self._rescan_pending, - "ignore": self._ignore, "dl_pending": self._dl_pending, "queue": self._queue_handler, "unsubscribe": self._unsubscribe, @@ -112,15 +111,6 @@ class PostData: update_subscribed.delay() return {"success": True} - def _ignore(self): - """ignore from download queue""" - video_id = self.exec_val - print(f"{video_id}: ignore video from download queue") - PendingInteract(video_id=video_id, status="ignore").update_status() - # also clear from redis queue - RedisQueue(queue_name="dl_queue").clear_item(video_id) - return {"success": True} - @staticmethod def _dl_pending(): """start the download queue""" diff --git a/tubearchivist/static/script.js b/tubearchivist/static/script.js index 6b919bf..edf3de4 100644 --- a/tubearchivist/static/script.js +++ b/tubearchivist/static/script.js @@ -186,8 +186,8 @@ function dlPending() { function toIgnore(button) { let youtube_id = button.getAttribute('data-id'); - let payload = JSON.stringify({ ignore: youtube_id }); - sendPost(payload); + let apiEndpoint = '/api/download/' + youtube_id + '/'; + apiRequest(apiEndpoint, 'POST', {status: 'ignore'}); document.getElementById('dl-' + youtube_id).remove(); } From 470ecb007dd6beb51071fd3416f10aec2b43d6b1 Mon Sep 17 00:00:00 2001 From: simon Date: Thu, 22 Dec 2022 19:48:24 +0700 Subject: [PATCH 10/16] switch re-add to queue to API call --- tubearchivist/home/src/frontend/api_calls.py | 8 -------- tubearchivist/static/script.js | 4 ++-- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/tubearchivist/home/src/frontend/api_calls.py b/tubearchivist/home/src/frontend/api_calls.py index 47d68b3..4e0a92c 100644 --- a/tubearchivist/home/src/frontend/api_calls.py +++ b/tubearchivist/home/src/frontend/api_calls.py @@ -62,7 +62,6 @@ class PostData: "show_subed_only": self._show_subed_only, "dlnow": self._dlnow, "show_ignored_only": self._show_ignored_only, - "addSingle": self._add_single, "deleteQueue": self._delete_queue, "manual-import": self._manual_import, "re-embed": self._re_embed, @@ -213,13 +212,6 @@ class PostData: RedisArchivist().set_message(key, value) return {"success": True} - def _add_single(self): - """add single youtube_id to download queue""" - video_id = self.exec_val - print(f"{video_id}: add single vid to download queue") - PendingInteract(video_id=video_id, status="pending").update_status() - return {"success": True} - def _delete_queue(self): """delete download queue""" status = self.exec_val diff --git a/tubearchivist/static/script.js b/tubearchivist/static/script.js index edf3de4..4ec6dea 100644 --- a/tubearchivist/static/script.js +++ b/tubearchivist/static/script.js @@ -210,8 +210,8 @@ function forgetIgnore(button) { function addSingle(button) { let youtube_id = button.getAttribute('data-id'); - let payload = JSON.stringify({ addSingle: youtube_id }); - sendPost(payload); + let apiEndpoint = '/api/download/' + youtube_id + '/'; + apiRequest(apiEndpoint, 'POST', {status: 'pending'}) document.getElementById('dl-' + youtube_id).remove(); setTimeout(function () { checkMessages(); From 09556dbf21a26fe7b649e7568dae159f0fe70dd5 Mon Sep 17 00:00:00 2001 From: simon Date: Thu, 22 Dec 2022 19:57:19 +0700 Subject: [PATCH 11/16] switch deleteQueue to API call --- tubearchivist/home/src/frontend/api_calls.py | 9 --------- tubearchivist/static/script.js | 6 +++--- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/tubearchivist/home/src/frontend/api_calls.py b/tubearchivist/home/src/frontend/api_calls.py index 4e0a92c..48df366 100644 --- a/tubearchivist/home/src/frontend/api_calls.py +++ b/tubearchivist/home/src/frontend/api_calls.py @@ -4,7 +4,6 @@ Functionality: - called via user input """ -from home.src.download.queue import PendingInteract from home.src.download.subscriptions import ( ChannelSubscription, PlaylistSubscription, @@ -62,7 +61,6 @@ class PostData: "show_subed_only": self._show_subed_only, "dlnow": self._dlnow, "show_ignored_only": self._show_ignored_only, - "deleteQueue": self._delete_queue, "manual-import": self._manual_import, "re-embed": self._re_embed, "db-backup": self._db_backup, @@ -212,13 +210,6 @@ class PostData: RedisArchivist().set_message(key, value) return {"success": True} - def _delete_queue(self): - """delete download queue""" - status = self.exec_val - print("deleting from download queue: " + status) - PendingInteract(status=status).delete_by_status() - return {"success": True} - @staticmethod def _manual_import(): """run manual import from settings page""" diff --git a/tubearchivist/static/script.js b/tubearchivist/static/script.js index 4ec6dea..1883c01 100644 --- a/tubearchivist/static/script.js +++ b/tubearchivist/static/script.js @@ -204,7 +204,7 @@ function downloadNow(button) { function forgetIgnore(button) { let youtube_id = button.getAttribute('data-id'); let apiEndpoint = '/api/download/' + youtube_id + '/'; - apiRequest(apiEndpoint, "DELETE"); + apiRequest(apiEndpoint, 'DELETE'); document.getElementById('dl-' + youtube_id).remove(); } @@ -220,8 +220,8 @@ function addSingle(button) { function deleteQueue(button) { let to_delete = button.getAttribute('data-id'); - let payload = JSON.stringify({ deleteQueue: to_delete }); - sendPost(payload); + let apiEndpoint = '/api/download/?filter=' + to_delete; + apiRequest(apiEndpoint, 'DELETE'); // clear button let message = document.createElement('p'); message.innerText = 'deleting download queue: ' + to_delete; From a2e2fd1b89a341bac5232cb4b3d98be5ce4618be Mon Sep 17 00:00:00 2001 From: simon Date: Fri, 23 Dec 2022 11:08:30 +0700 Subject: [PATCH 12/16] add missing trailing / for endpoints docs --- tubearchivist/api/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tubearchivist/api/README.md b/tubearchivist/api/README.md index e26978b..f6c5aea 100644 --- a/tubearchivist/api/README.md +++ b/tubearchivist/api/README.md @@ -89,12 +89,12 @@ DELETE: /api/video/\/ /api/video/\/similar/ ## Video Progress View -/api/video/\/progress +/api/video/\/progress/ Progress is stored for each user. ### Get last player position of a video -GET /api/video/\/progress +GET /api/video/\/progress/ ```json { "youtube_id": "", @@ -104,7 +104,7 @@ GET /api/video/\/progress ``` ### Post player position of video -POST /api/video/\/progress +POST /api/video/\/progress/ ```json { "position": 100 @@ -112,7 +112,7 @@ POST /api/video/\/progress ``` ### Delete player position of video -DELETE /api/video/\/progress +DELETE /api/video/\/progress/ ## Sponsor Block View @@ -267,7 +267,7 @@ Remove this snapshot from index ## Login View Return token and user ID for username and password: -POST /api/login +POST /api/login/ ```json { "username": "tubearchivist", @@ -406,7 +406,7 @@ Returns search results from your query. ## Ping View Validate your connection with the API -GET /api/ping +GET /api/ping/ When valid returns message with user id: ```json From 6ed2308f99d3d4b81b1feefb2e33a6c74a6a5a9b Mon Sep 17 00:00:00 2001 From: simon Date: Fri, 23 Dec 2022 20:39:14 +0700 Subject: [PATCH 13/16] [API] add watched state endpoints --- tubearchivist/api/README.md | 14 ++ tubearchivist/api/urls.py | 6 + tubearchivist/api/views.py | 19 +++ tubearchivist/home/src/frontend/api_calls.py | 4 +- tubearchivist/home/src/frontend/watched.py | 131 +++++++++---------- tubearchivist/static/script.js | 12 +- 6 files changed, 112 insertions(+), 74 deletions(-) diff --git a/tubearchivist/api/README.md b/tubearchivist/api/README.md index f6c5aea..1a95fb8 100644 --- a/tubearchivist/api/README.md +++ b/tubearchivist/api/README.md @@ -41,6 +41,7 @@ Note: - [Refresh](#refresh-view) - [Cookie](#cookie-view) - [Search](#search-view) +- [Watched](#watched-view) - [Ping](#ping-view) ## Authentication @@ -404,6 +405,19 @@ GET /api/search/?query=\ Returns search results from your query. +## Watched View +POST /api/watched/ + +Change watched state, where the `id` can be a single video, or channel/playlist to change all videos belonging to that channel/playlist. + +```json +{ + "id": "xxxxxxx", + "is_watched": True +} +``` + + ## Ping View Validate your connection with the API GET /api/ping/ diff --git a/tubearchivist/api/urls.py b/tubearchivist/api/urls.py index 0476719..7cf5368 100644 --- a/tubearchivist/api/urls.py +++ b/tubearchivist/api/urls.py @@ -23,6 +23,7 @@ from api.views import ( VideoProgressView, VideoSimilarView, VideoSponsorView, + WatchedView, ) from django.urls import path @@ -124,6 +125,11 @@ urlpatterns = [ CookieView.as_view(), name="api-cookie", ), + path( + "watched/", + WatchedView.as_view(), + name="api-watched", + ), path( "search/", SearchView.as_view(), diff --git a/tubearchivist/api/views.py b/tubearchivist/api/views.py index e24c2ef..c99871d 100644 --- a/tubearchivist/api/views.py +++ b/tubearchivist/api/views.py @@ -7,6 +7,7 @@ from home.src.download.yt_dlp_base import CookieHandler from home.src.es.connect import ElasticWrap from home.src.es.snapshot import ElasticSnapshot from home.src.frontend.searching import SearchForm +from home.src.frontend.watched import WatchState from home.src.index.channel import YoutubeChannel from home.src.index.generic import Pagination from home.src.index.reindex import ReindexProgress @@ -703,6 +704,24 @@ class CookieView(ApiBaseView): return Response(message) +class WatchedView(ApiBaseView): + """resolves to /api/watched/ + POST: change watched state of video, channel or playlist + """ + + def post(self, request): + """change watched state""" + youtube_id = request.data.get("id") + is_watched = request.data.get("is_watched") + + if not youtube_id or is_watched is None: + message = {"message": "missing id or is_watched"} + return Response(message, status=400) + + WatchState(youtube_id, is_watched).change() + return Response({"message": "success"}, status=200) + + class SearchView(ApiBaseView): """resolves to /api/search/ GET: run a search with the string in the ?query parameter diff --git a/tubearchivist/home/src/frontend/api_calls.py b/tubearchivist/home/src/frontend/api_calls.py index 48df366..3ed129f 100644 --- a/tubearchivist/home/src/frontend/api_calls.py +++ b/tubearchivist/home/src/frontend/api_calls.py @@ -74,12 +74,12 @@ class PostData: def _watched(self): """mark as watched""" - WatchState(self.exec_val).mark_as_watched() + WatchState(self.exec_val, is_watched=True).change() return {"success": True} def _un_watched(self): """mark as unwatched""" - WatchState(self.exec_val).mark_as_unwatched() + WatchState(self.exec_val, is_watched=False).change() return {"success": True} def _change_view(self): diff --git a/tubearchivist/home/src/frontend/watched.py b/tubearchivist/home/src/frontend/watched.py index e8b3e15..8978b96 100644 --- a/tubearchivist/home/src/frontend/watched.py +++ b/tubearchivist/home/src/frontend/watched.py @@ -12,95 +12,94 @@ from home.src.ta.helper import UrlListParser class WatchState: """handle watched checkbox for videos and channels""" - def __init__(self, youtube_id): + def __init__(self, youtube_id, is_watched): self.youtube_id = youtube_id + self.is_watched = is_watched self.stamp = int(datetime.now().timestamp()) + self.pipeline = f"_ingest/pipeline/watch_{youtube_id}" - def mark_as_watched(self): - """update es with new watched value""" - url_type = self.dedect_type() + def change(self): + """change watched state of item(s)""" + url_type = self._dedect_type() if url_type == "video": - self.mark_vid_watched() - elif url_type == "channel": - self.mark_channel_watched() - elif url_type == "playlist": - self.mark_playlist_watched() + self.change_vid_state() + return - print(f"{self.youtube_id}: marked as watched") + self._add_pipeline() + path = f"ta_video/_update_by_query?pipeline=watch_{self.youtube_id}" + data = self._build_update_data(url_type) + _, _ = ElasticWrap(path).post(data) + self._delete_pipeline() - def mark_as_unwatched(self): - """revert watched state to false""" - url_type = self.dedect_type() - if url_type == "video": - self.mark_vid_watched(revert=True) - - print(f"{self.youtube_id}: revert as unwatched") - - def dedect_type(self): + def _dedect_type(self): """find youtube id type""" print(self.youtube_id) url_process = UrlListParser(self.youtube_id).process_list() url_type = url_process[0]["type"] return url_type - def mark_vid_watched(self, revert=False): - """change watched status of single video""" + def change_vid_state(self): + """change watched state of video""" path = f"ta_video/_update/{self.youtube_id}" data = { - "doc": {"player": {"watched": True, "watched_date": self.stamp}} + "doc": { + "player": { + "watched": self.is_watched, + "watched_date": self.stamp, + } + } } - if revert: - data["doc"]["player"]["watched"] = False - response, status_code = ElasticWrap(path).post(data=data) if status_code != 200: print(response) raise ValueError("failed to mark video as watched") - def _get_source(self): - """build source line for update_by_query script""" - source = [ - "ctx._source.player['watched'] = true", - f"ctx._source.player['watched_date'] = {self.stamp}", - ] - return "; ".join(source) + def _build_update_data(self, url_type): + """build update by query data based on url_type""" + term_key_map = { + "channel": "channel.channel_id", + "playlist": "playlist.keyword", + } + term_key = term_key_map.get(url_type) - def mark_channel_watched(self): - """change watched status of every video in channel""" - path = "ta_video/_update_by_query" - must_list = [ - {"term": {"channel.channel_id": {"value": self.youtube_id}}}, - {"term": {"player.watched": {"value": False}}}, - ] - data = { - "query": {"bool": {"must": must_list}}, - "script": { - "source": self._get_source(), - "lang": "painless", - }, + return { + "query": { + "bool": { + "must": [ + {"term": {term_key: {"value": self.youtube_id}}}, + { + "term": { + "player.watched": { + "value": not self.is_watched + } + } + }, + ], + } + } } - response, status_code = ElasticWrap(path).post(data=data) - if status_code != 200: - print(response) - raise ValueError("failed mark channel as watched") - - def mark_playlist_watched(self): - """change watched state of all videos in playlist""" - path = "ta_video/_update_by_query" - must_list = [ - {"term": {"playlist.keyword": {"value": self.youtube_id}}}, - {"term": {"player.watched": {"value": False}}}, - ] + def _add_pipeline(self): + """add ingest pipeline""" data = { - "query": {"bool": {"must": must_list}}, - "script": { - "source": self._get_source(), - "lang": "painless", - }, + "description": f"{self.youtube_id}: watched {self.is_watched}", + "processors": [ + { + "set": { + "field": "player.watched", + "value": self.is_watched, + } + }, + { + "set": { + "field": "player.watched_date", + "value": self.stamp, + } + }, + ], } + _, _ = ElasticWrap(self.pipeline).put(data) - response, status_code = ElasticWrap(path).post(data=data) - if status_code != 200: - print(response) - raise ValueError("failed mark playlist as watched") + def _delete_pipeline(self): + """delete pipeline""" + ElasticWrap(self.pipeline).delete() diff --git a/tubearchivist/static/script.js b/tubearchivist/static/script.js index 1883c01..cb993f4 100644 --- a/tubearchivist/static/script.js +++ b/tubearchivist/static/script.js @@ -24,14 +24,13 @@ function updateVideoWatchStatus(input1, videoCurrentWatchStatus) { removeProgressBar(videoId); let watchStatusIndicator, payload; + let apiEndpoint = '/api/watched/' if (videoCurrentWatchStatus === 'watched') { watchStatusIndicator = createWatchStatusIndicator(videoId, 'unwatched'); - payload = JSON.stringify({ un_watched: videoId }); - sendPost(payload); + apiRequest(apiEndpoint, 'POST', {id: videoId, "is_watched": false}) } else if (videoCurrentWatchStatus === 'unwatched') { watchStatusIndicator = createWatchStatusIndicator(videoId, 'watched'); - payload = JSON.stringify({ watched: videoId }); - sendPost(payload); + apiRequest(apiEndpoint, 'POST', {id: videoId, "is_watched": true}) } let watchButtons = document.getElementsByClassName('watch-button'); @@ -76,9 +75,10 @@ function removeProgressBar(videoId) { function isWatchedButton(button) { let youtube_id = button.getAttribute('data-id'); - let payload = JSON.stringify({ watched: youtube_id }); + let apiEndpoint = '/api/watched/'; + let data = {id: youtube_id, is_watched: true} button.remove(); - sendPost(payload); + apiRequest(apiEndpoint, 'POST', data); setTimeout(function () { location.reload(); }, 1000); From 0b60377e197eea6ac5b7f648767f68b8e00f4a41 Mon Sep 17 00:00:00 2001 From: simon Date: Fri, 23 Dec 2022 20:43:36 +0700 Subject: [PATCH 14/16] cleanup, fix lint --- package-lock.json | 348 +++++++++++++++---- tubearchivist/home/src/frontend/api_calls.py | 13 - tubearchivist/static/script.js | 14 +- 3 files changed, 278 insertions(+), 97 deletions(-) diff --git a/package-lock.json b/package-lock.json index 22e8152..c32eaea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,12 @@ { - "name": "tubearchivist", + "name": "tubearchivist_project", "lockfileVersion": 2, "requires": true, "packages": { "": { - "dependencies": { - "eslint-config-prettier": "^8.5.0" - }, "devDependencies": { "eslint": "^8.26.0", + "eslint-config-prettier": "^8.5.0", "prettier": "^2.7.1" } }, @@ -16,6 +14,7 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", "integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==", + "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -38,6 +37,7 @@ "version": "0.11.6", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.6.tgz", "integrity": "sha512-jJr+hPTJYKyDILJfhNSHsjiwXYf26Flsz8DvNndOsHs5pwSnpGUEy8yzF0JYhCEvTDdV2vuOK5tt8BVhwO5/hg==", + "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^1.2.1", "debug": "^4.1.1", @@ -51,6 +51,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, "engines": { "node": ">=12.22" }, @@ -62,12 +63,14 @@ "node_modules/@humanwhocodes/object-schema": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -80,6 +83,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, "engines": { "node": ">= 8" } @@ -88,6 +92,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -100,6 +105,7 @@ "version": "8.8.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "dev": true, "bin": { "acorn": "bin/acorn" }, @@ -111,6 +117,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -119,6 +126,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -134,6 +142,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "engines": { "node": ">=8" } @@ -142,6 +151,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -155,17 +165,20 @@ "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -175,6 +188,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, "engines": { "node": ">=6" } @@ -183,6 +197,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -198,6 +213,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -208,17 +224,20 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -232,6 +251,7 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -247,12 +267,14 @@ "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, "dependencies": { "esutils": "^2.0.2" }, @@ -264,6 +286,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, "engines": { "node": ">=10" }, @@ -275,6 +298,7 @@ "version": "8.26.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.26.0.tgz", "integrity": "sha512-kzJkpaw1Bfwheq4VXUezFriD1GxszX6dUekM7Z3aC2o4hju+tsR/XyTC3RcoSD7jmy9VkPU3+N6YjVU2e96Oyg==", + "dev": true, "dependencies": { "@eslint/eslintrc": "^1.3.3", "@humanwhocodes/config-array": "^0.11.6", @@ -330,6 +354,7 @@ "version": "8.5.0", "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", + "dev": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -341,6 +366,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -353,6 +379,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, "dependencies": { "eslint-visitor-keys": "^2.0.0" }, @@ -370,6 +397,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, "engines": { "node": ">=10" } @@ -378,6 +406,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } @@ -386,6 +415,7 @@ "version": "9.4.0", "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz", "integrity": "sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==", + "dev": true, "dependencies": { "acorn": "^8.8.0", "acorn-jsx": "^5.3.2", @@ -402,6 +432,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, "dependencies": { "estraverse": "^5.1.0" }, @@ -413,6 +444,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, "dependencies": { "estraverse": "^5.2.0" }, @@ -424,6 +456,7 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, "engines": { "node": ">=4.0" } @@ -432,6 +465,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -439,22 +473,26 @@ "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true }, "node_modules/fastq": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, "dependencies": { "reusify": "^1.0.4" } @@ -463,6 +501,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, "dependencies": { "flat-cache": "^3.0.4" }, @@ -474,6 +513,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -489,6 +529,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, "dependencies": { "flatted": "^3.1.0", "rimraf": "^3.0.2" @@ -500,17 +541,20 @@ "node_modules/flatted": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -530,6 +574,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, "dependencies": { "is-glob": "^4.0.3" }, @@ -541,6 +586,7 @@ "version": "13.17.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", + "dev": true, "dependencies": { "type-fest": "^0.20.2" }, @@ -554,12 +600,14 @@ "node_modules/grapheme-splitter": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==" + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, "engines": { "node": ">=8" } @@ -568,6 +616,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true, "engines": { "node": ">= 4" } @@ -576,6 +625,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -591,6 +641,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, "engines": { "node": ">=0.8.19" } @@ -599,6 +650,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -607,12 +659,14 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -621,6 +675,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -632,6 +687,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, "engines": { "node": ">=8" } @@ -639,17 +695,20 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true }, "node_modules/js-sdsl": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.5.tgz", - "integrity": "sha512-08bOAKweV2NUC1wqTtf3qZlnpOX/R2DU9ikpjOHs0H+ibQv3zpncVQg6um4uYtRtrwIX8M4Nh3ytK4HGlYAq7Q==" + "integrity": "sha512-08bOAKweV2NUC1wqTtf3qZlnpOX/R2DU9ikpjOHs0H+ibQv3zpncVQg6um4uYtRtrwIX8M4Nh3ytK4HGlYAq7Q==", + "dev": true }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, "dependencies": { "argparse": "^2.0.1" }, @@ -660,17 +719,20 @@ "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -683,6 +745,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, "dependencies": { "p-locate": "^5.0.0" }, @@ -696,12 +759,14 @@ "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -712,17 +777,20 @@ "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, "dependencies": { "wrappy": "1" } @@ -731,6 +799,7 @@ "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", @@ -747,6 +816,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, "dependencies": { "yocto-queue": "^0.1.0" }, @@ -761,6 +831,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, "dependencies": { "p-limit": "^3.0.2" }, @@ -775,6 +846,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, "dependencies": { "callsites": "^3.0.0" }, @@ -786,6 +858,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, "engines": { "node": ">=8" } @@ -794,6 +867,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -802,6 +876,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, "engines": { "node": ">=8" } @@ -810,6 +885,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, "engines": { "node": ">= 0.8.0" } @@ -833,6 +909,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, "engines": { "node": ">=6" } @@ -841,6 +918,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, "funding": [ { "type": "github", @@ -860,6 +938,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, "engines": { "node": ">=8" }, @@ -871,6 +950,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, "engines": { "node": ">=4" } @@ -879,6 +959,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -888,6 +969,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, "dependencies": { "glob": "^7.1.3" }, @@ -902,6 +984,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, "funding": [ { "type": "github", @@ -924,6 +1007,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -935,6 +1019,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, "engines": { "node": ">=8" } @@ -943,6 +1028,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -954,6 +1040,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, "engines": { "node": ">=8" }, @@ -965,6 +1052,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -975,12 +1063,14 @@ "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, "dependencies": { "prelude-ls": "^1.2.1" }, @@ -992,6 +1082,7 @@ "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, "engines": { "node": ">=10" }, @@ -1003,6 +1094,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, "dependencies": { "punycode": "^2.1.0" } @@ -1011,6 +1103,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -1025,6 +1118,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -1032,12 +1126,14 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, "engines": { "node": ">=10" }, @@ -1051,6 +1147,7 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", "integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==", + "dev": true, "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -1067,6 +1164,7 @@ "version": "0.11.6", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.6.tgz", "integrity": "sha512-jJr+hPTJYKyDILJfhNSHsjiwXYf26Flsz8DvNndOsHs5pwSnpGUEy8yzF0JYhCEvTDdV2vuOK5tt8BVhwO5/hg==", + "dev": true, "requires": { "@humanwhocodes/object-schema": "^1.2.1", "debug": "^4.1.1", @@ -1076,17 +1174,20 @@ "@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==" + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true }, "@humanwhocodes/object-schema": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, "requires": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -1095,12 +1196,14 @@ "@nodelib/fs.stat": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==" + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true }, "@nodelib/fs.walk": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, "requires": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -1109,18 +1212,21 @@ "acorn": { "version": "8.8.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==" + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "dev": true }, "acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, "requires": {} }, "ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -1131,12 +1237,14 @@ "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "requires": { "color-convert": "^2.0.1" } @@ -1144,17 +1252,20 @@ "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1163,12 +1274,14 @@ "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -1178,6 +1291,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "requires": { "color-name": "~1.1.4" } @@ -1185,17 +1299,20 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, "requires": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -1206,6 +1323,7 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, "requires": { "ms": "2.1.2" } @@ -1213,12 +1331,14 @@ "deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, "requires": { "esutils": "^2.0.2" } @@ -1226,12 +1346,14 @@ "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true }, "eslint": { "version": "8.26.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.26.0.tgz", "integrity": "sha512-kzJkpaw1Bfwheq4VXUezFriD1GxszX6dUekM7Z3aC2o4hju+tsR/XyTC3RcoSD7jmy9VkPU3+N6YjVU2e96Oyg==", + "dev": true, "requires": { "@eslint/eslintrc": "^1.3.3", "@humanwhocodes/config-array": "^0.11.6", @@ -1278,12 +1400,14 @@ "version": "8.5.0", "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", + "dev": true, "requires": {} }, "eslint-scope": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, "requires": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -1293,6 +1417,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, "requires": { "eslint-visitor-keys": "^2.0.0" }, @@ -1300,19 +1425,22 @@ "eslint-visitor-keys": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==" + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true } } }, "eslint-visitor-keys": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==" + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true }, "espree": { "version": "9.4.0", "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz", "integrity": "sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==", + "dev": true, "requires": { "acorn": "^8.8.0", "acorn-jsx": "^5.3.2", @@ -1323,6 +1451,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, "requires": { "estraverse": "^5.1.0" } @@ -1331,6 +1460,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, "requires": { "estraverse": "^5.2.0" } @@ -1338,32 +1468,38 @@ "estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true }, "esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true }, "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true }, "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true }, "fastq": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, "requires": { "reusify": "^1.0.4" } @@ -1372,6 +1508,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, "requires": { "flat-cache": "^3.0.4" } @@ -1380,6 +1517,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, "requires": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -1389,6 +1527,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, "requires": { "flatted": "^3.1.0", "rimraf": "^3.0.2" @@ -1397,17 +1536,20 @@ "flatted": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true }, "glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -1421,6 +1563,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, "requires": { "is-glob": "^4.0.3" } @@ -1429,6 +1572,7 @@ "version": "13.17.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", + "dev": true, "requires": { "type-fest": "^0.20.2" } @@ -1436,22 +1580,26 @@ "grapheme-splitter": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==" + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true }, "ignore": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==" + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true }, "import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, "requires": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -1460,12 +1608,14 @@ "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==" + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -1474,17 +1624,20 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true }, "is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, "requires": { "is-extglob": "^2.1.1" } @@ -1492,22 +1645,26 @@ "is-path-inside": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==" + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true }, "js-sdsl": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.5.tgz", - "integrity": "sha512-08bOAKweV2NUC1wqTtf3qZlnpOX/R2DU9ikpjOHs0H+ibQv3zpncVQg6um4uYtRtrwIX8M4Nh3ytK4HGlYAq7Q==" + "integrity": "sha512-08bOAKweV2NUC1wqTtf3qZlnpOX/R2DU9ikpjOHs0H+ibQv3zpncVQg6um4uYtRtrwIX8M4Nh3ytK4HGlYAq7Q==", + "dev": true }, "js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, "requires": { "argparse": "^2.0.1" } @@ -1515,17 +1672,20 @@ "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true }, "levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, "requires": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -1535,6 +1695,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, "requires": { "p-locate": "^5.0.0" } @@ -1542,12 +1703,14 @@ "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true }, "minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -1555,17 +1718,20 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, "requires": { "wrappy": "1" } @@ -1574,6 +1740,7 @@ "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, "requires": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", @@ -1587,6 +1754,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, "requires": { "yocto-queue": "^0.1.0" } @@ -1595,6 +1763,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, "requires": { "p-limit": "^3.0.2" } @@ -1603,6 +1772,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, "requires": { "callsites": "^3.0.0" } @@ -1610,22 +1780,26 @@ "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==" + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true }, "prettier": { "version": "2.7.1", @@ -1636,32 +1810,38 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true }, "regexpp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==" + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true }, "resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true }, "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, "requires": { "glob": "^7.1.3" } @@ -1670,6 +1850,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, "requires": { "queue-microtask": "^1.2.2" } @@ -1678,6 +1859,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, "requires": { "shebang-regex": "^3.0.0" } @@ -1685,12 +1867,14 @@ "shebang-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "requires": { "ansi-regex": "^5.0.1" } @@ -1698,12 +1882,14 @@ "strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "requires": { "has-flag": "^4.0.0" } @@ -1711,12 +1897,14 @@ "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, "requires": { "prelude-ls": "^1.2.1" } @@ -1724,12 +1912,14 @@ "type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==" + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, "requires": { "punycode": "^2.1.0" } @@ -1738,6 +1928,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "requires": { "isexe": "^2.0.0" } @@ -1745,17 +1936,20 @@ "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true }, "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true } } } diff --git a/tubearchivist/home/src/frontend/api_calls.py b/tubearchivist/home/src/frontend/api_calls.py index 3ed129f..9695792 100644 --- a/tubearchivist/home/src/frontend/api_calls.py +++ b/tubearchivist/home/src/frontend/api_calls.py @@ -8,7 +8,6 @@ from home.src.download.subscriptions import ( ChannelSubscription, PlaylistSubscription, ) -from home.src.frontend.watched import WatchState from home.src.index.playlist import YoutubePlaylist from home.src.ta.helper import UrlListParser from home.src.ta.ta_redis import RedisArchivist, RedisQueue @@ -47,8 +46,6 @@ class PostData: def exec_map(self): """map dict key and return function to execute""" exec_map = { - "watched": self._watched, - "un_watched": self._un_watched, "change_view": self._change_view, "change_grid": self._change_grid, "rescan_pending": self._rescan_pending, @@ -72,16 +69,6 @@ class PostData: return exec_map[self.to_exec] - def _watched(self): - """mark as watched""" - WatchState(self.exec_val, is_watched=True).change() - return {"success": True} - - def _un_watched(self): - """mark as unwatched""" - WatchState(self.exec_val, is_watched=False).change() - return {"success": True} - def _change_view(self): """process view changes in home, channel, and downloads""" origin, new_view = self.exec_val.split(":") diff --git a/tubearchivist/static/script.js b/tubearchivist/static/script.js index cb993f4..56174c4 100644 --- a/tubearchivist/static/script.js +++ b/tubearchivist/static/script.js @@ -23,14 +23,14 @@ function updateVideoWatchStatus(input1, videoCurrentWatchStatus) { postVideoProgress(videoId, 0); // Reset video progress on watched/unwatched; removeProgressBar(videoId); - let watchStatusIndicator, payload; - let apiEndpoint = '/api/watched/' + let watchStatusIndicator; + let apiEndpoint = '/api/watched/'; if (videoCurrentWatchStatus === 'watched') { watchStatusIndicator = createWatchStatusIndicator(videoId, 'unwatched'); - apiRequest(apiEndpoint, 'POST', {id: videoId, "is_watched": false}) + apiRequest(apiEndpoint, 'POST', { id: videoId, is_watched: false }); } else if (videoCurrentWatchStatus === 'unwatched') { watchStatusIndicator = createWatchStatusIndicator(videoId, 'watched'); - apiRequest(apiEndpoint, 'POST', {id: videoId, "is_watched": true}) + apiRequest(apiEndpoint, 'POST', { id: videoId, is_watched: true }); } let watchButtons = document.getElementsByClassName('watch-button'); @@ -76,7 +76,7 @@ function removeProgressBar(videoId) { function isWatchedButton(button) { let youtube_id = button.getAttribute('data-id'); let apiEndpoint = '/api/watched/'; - let data = {id: youtube_id, is_watched: true} + let data = { id: youtube_id, is_watched: true }; button.remove(); apiRequest(apiEndpoint, 'POST', data); setTimeout(function () { @@ -187,7 +187,7 @@ function dlPending() { function toIgnore(button) { let youtube_id = button.getAttribute('data-id'); let apiEndpoint = '/api/download/' + youtube_id + '/'; - apiRequest(apiEndpoint, 'POST', {status: 'ignore'}); + apiRequest(apiEndpoint, 'POST', { status: 'ignore' }); document.getElementById('dl-' + youtube_id).remove(); } @@ -211,7 +211,7 @@ function forgetIgnore(button) { function addSingle(button) { let youtube_id = button.getAttribute('data-id'); let apiEndpoint = '/api/download/' + youtube_id + '/'; - apiRequest(apiEndpoint, 'POST', {status: 'pending'}) + apiRequest(apiEndpoint, 'POST', { status: 'pending' }); document.getElementById('dl-' + youtube_id).remove(); setTimeout(function () { checkMessages(); From 599dd26b53a7cca270de6bf1d21a644420660997 Mon Sep 17 00:00:00 2001 From: simon Date: Fri, 23 Dec 2022 22:34:25 +0700 Subject: [PATCH 15/16] refactor comment interface into reusable CommentList class --- .../home/src/download/yt_dlp_handler.py | 22 +---------- tubearchivist/home/src/index/comments.py | 39 ++++++++++++++++++- tubearchivist/home/src/index/filesystem.py | 7 +++- 3 files changed, 45 insertions(+), 23 deletions(-) diff --git a/tubearchivist/home/src/download/yt_dlp_handler.py b/tubearchivist/home/src/download/yt_dlp_handler.py index c5c452d..a0f01eb 100644 --- a/tubearchivist/home/src/download/yt_dlp_handler.py +++ b/tubearchivist/home/src/download/yt_dlp_handler.py @@ -15,7 +15,7 @@ from home.src.download.subscriptions import PlaylistSubscription from home.src.download.yt_dlp_base import CookieHandler, YtWrap from home.src.es.connect import ElasticWrap, IndexPaginate from home.src.index.channel import YoutubeChannel -from home.src.index.comments import Comments +from home.src.index.comments import CommentList from home.src.index.playlist import YoutubePlaylist from home.src.index.video import YoutubeVideo, index_new_video from home.src.ta.config import AppConfig @@ -143,25 +143,7 @@ class DownloadPostProcess: def get_comments(self): """get comments from youtube""" - if not self.download.config["downloads"]["comment_max"]: - return - - total_videos = len(self.download.videos) - for idx, video_id in enumerate(self.download.videos): - comment = Comments(video_id, config=self.download.config) - comment.build_json(notify=(idx, total_videos)) - if comment.json_data: - comment.upload_comments() - - key = "message:download" - message = { - "status": key, - "level": "info", - "title": "Download and index comments finished", - "message": f"added comments for {total_videos} videos", - } - - RedisArchivist().set_message(key, message, expire=4) + CommentList(self.download.videos).index(send_notifications=True) class VideoDownloader: diff --git a/tubearchivist/home/src/index/comments.py b/tubearchivist/home/src/index/comments.py index c512802..414d875 100644 --- a/tubearchivist/home/src/index/comments.py +++ b/tubearchivist/home/src/index/comments.py @@ -14,7 +14,7 @@ from home.src.ta.ta_redis import RedisArchivist class Comments: - """hold all comments functionality""" + """interact with comments per video""" def __init__(self, youtube_id, config=False): self.youtube_id = youtube_id @@ -187,3 +187,40 @@ class Comments: self.delete_comments() self.upload_comments() + + +class CommentList: + """interact with comments in group""" + + def __init__(self, video_ids): + self.video_ids = video_ids + self.config = AppConfig().config + + def index(self, notify=False): + """index group of videos""" + if not self.config["downloads"].get("comment_max"): + return + + total_videos = len(self.video_ids) + for idx, video_id in enumerate(self.video_ids): + comment = Comments(video_id, config=self.config) + if notify: + notify = (idx, total_videos) + comment.build_json(notify=notify) + if comment.json_data: + comment.upload_comments() + + if notify: + self.notify_final(total_videos) + + @staticmethod + def notify_final(total_videos): + """send final notification""" + key = "message:download" + message = { + "status": key, + "level": "info", + "title": "Download and index comments finished", + "message": f"added comments for {total_videos} videos", + } + RedisArchivist().set_message(key, message, expire=4) diff --git a/tubearchivist/home/src/index/filesystem.py b/tubearchivist/home/src/index/filesystem.py index 38ca3e9..5f496ae 100644 --- a/tubearchivist/home/src/index/filesystem.py +++ b/tubearchivist/home/src/index/filesystem.py @@ -14,6 +14,7 @@ import subprocess from home.src.download.queue import PendingList from home.src.download.thumbnails import ThumbManager from home.src.es.connect import ElasticWrap +from home.src.index.comments import CommentList from home.src.index.video import YoutubeVideo, index_new_video from home.src.ta.config import AppConfig from home.src.ta.helper import clean_string, ignore_filelist @@ -601,6 +602,8 @@ def scan_filesystem(): filesystem_handler.delete_from_index() if filesystem_handler.to_index: print("index new videos") - for missing_vid in filesystem_handler.to_index: - youtube_id = missing_vid[2] + video_ids = [i[2] for i in filesystem_handler.to_index] + for youtube_id in video_ids: index_new_video(youtube_id) + + CommentList(video_ids).index() From 73e1bba541d51156d46e1f0531405fc7cafa3072 Mon Sep 17 00:00:00 2001 From: simon Date: Fri, 23 Dec 2022 23:00:35 +0700 Subject: [PATCH 16/16] fix refactor codelist init --- tubearchivist/home/src/download/yt_dlp_handler.py | 2 +- tubearchivist/home/src/index/comments.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tubearchivist/home/src/download/yt_dlp_handler.py b/tubearchivist/home/src/download/yt_dlp_handler.py index a0f01eb..c25b161 100644 --- a/tubearchivist/home/src/download/yt_dlp_handler.py +++ b/tubearchivist/home/src/download/yt_dlp_handler.py @@ -143,7 +143,7 @@ class DownloadPostProcess: def get_comments(self): """get comments from youtube""" - CommentList(self.download.videos).index(send_notifications=True) + CommentList(self.download.videos).index(notify=True) class VideoDownloader: diff --git a/tubearchivist/home/src/index/comments.py b/tubearchivist/home/src/index/comments.py index 414d875..29bdeff 100644 --- a/tubearchivist/home/src/index/comments.py +++ b/tubearchivist/home/src/index/comments.py @@ -146,6 +146,7 @@ class Comments: if not self.is_activated: return + print(f"{self.youtube_id}: upload comments") _, _ = ElasticWrap(self.es_path).put(self.json_data) vid_path = f"ta_video/_update/{self.youtube_id}"