From 393f704e521efc204bf761aa026178e107acf1d5 Mon Sep 17 00:00:00 2001 From: lonelyhentxi Date: Tue, 31 Dec 2024 23:56:00 +0800 Subject: [PATCH] fix: refactor config --- .vscode/launch.json | 10 +- Cargo.lock | 114 ++++++++++++---- Cargo.toml | 2 +- apps/api/.env.development | 14 -- apps/api/.env.example | 15 --- apps/api/.gitignore | 45 ------- apps/api/app/apple-icon.png | Bin 216 -> 0 bytes apps/api/app/global-error.tsx | 29 ---- apps/api/app/icon.png | Bin 96 -> 0 bytes apps/api/app/layout.tsx | 13 -- apps/api/app/opengraph-image.png | Bin 58106 -> 0 bytes apps/api/instrumentation.ts | 3 - apps/api/next.config.ts | 15 --- apps/api/package.json | 37 ----- apps/api/sentry.client.config.ts | 34 ----- apps/api/tsconfig.json | 17 --- apps/app/.env.development | 8 +- apps/app/.env.example | 6 +- .../{api => app}/app/cron/keep-alive/route.ts | 0 apps/{api => app}/app/health/route.ts | 0 apps/app/package.json | 2 +- apps/{api => app}/vercel.json | 0 apps/docs/development.mdx | 6 +- apps/docs/package.json | 2 +- apps/email-playground/package.json | 2 +- apps/proxy/.whistle/rules/files/0.webui | 4 +- apps/recorder/.devcontainer/devcontainer.json | 14 +- apps/recorder/Cargo.toml | 5 +- apps/recorder/src/app.rs | 2 +- apps/recorder/src/auth/oidc.rs | 4 +- apps/recorder/src/auth/service.rs | 2 +- apps/recorder/src/config/mod.rs | 27 +++- apps/recorder/src/config/settings_mixin.yaml | 12 ++ apps/recorder/src/dal/client.rs | 2 +- apps/recorder/src/extract/mikan/client.rs | 12 +- apps/recorder/src/extract/mikan/config.rs | 6 +- apps/recorder/src/extract/mikan/rss_parser.rs | 8 +- apps/recorder/src/extract/mikan/web_parser.rs | 13 +- apps/recorder/src/fetch/bytes.rs | 23 +--- apps/recorder/src/fetch/client.rs | 101 +++++++++----- apps/recorder/src/fetch/html.rs | 23 +--- apps/recorder/src/fetch/image.rs | 16 +-- apps/recorder/src/fetch/mod.rs | 5 +- apps/storybook/README.md | 4 +- apps/web/.env.development | 8 +- apps/web/.env.example | 8 +- apps/web/package.json | 2 +- config/test.yaml | 2 +- docker-compose.yaml | 2 +- packages/{torrent => dlsignal}/.gitignore | 0 packages/{torrent => dlsignal}/Cargo.toml | 4 +- packages/{torrent => dlsignal}/src/core.rs | 0 packages/{torrent => dlsignal}/src/error.rs | 0 packages/{torrent => dlsignal}/src/lib.rs | 0 packages/{torrent => dlsignal}/src/qbit.rs | 0 pnpm-lock.yaml | 127 ------------------ 56 files changed, 274 insertions(+), 536 deletions(-) delete mode 100644 apps/api/.env.development delete mode 100644 apps/api/.env.example delete mode 100644 apps/api/.gitignore delete mode 100644 apps/api/app/apple-icon.png delete mode 100644 apps/api/app/global-error.tsx delete mode 100644 apps/api/app/icon.png delete mode 100644 apps/api/app/layout.tsx delete mode 100644 apps/api/app/opengraph-image.png delete mode 100644 apps/api/instrumentation.ts delete mode 100644 apps/api/next.config.ts delete mode 100644 apps/api/package.json delete mode 100644 apps/api/sentry.client.config.ts delete mode 100644 apps/api/tsconfig.json rename apps/{api => app}/app/cron/keep-alive/route.ts (100%) rename apps/{api => app}/app/health/route.ts (100%) rename apps/{api => app}/vercel.json (100%) create mode 100644 apps/recorder/src/config/settings_mixin.yaml rename packages/{torrent => dlsignal}/.gitignore (100%) rename packages/{torrent => dlsignal}/Cargo.toml (95%) rename packages/{torrent => dlsignal}/src/core.rs (100%) rename packages/{torrent => dlsignal}/src/error.rs (100%) rename packages/{torrent => dlsignal}/src/lib.rs (100%) rename packages/{torrent => dlsignal}/src/qbit.rs (100%) diff --git a/.vscode/launch.json b/.vscode/launch.json index 057362d..c2f73bb 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -94,31 +94,31 @@ "name": "Next.js: debug client-side (app)", "type": "chrome", "request": "launch", - "url": "http://localhost:3000" + "url": "http://localhost:5000" }, { "name": "Next.js: debug client-side (web)", "type": "chrome", "request": "launch", - "url": "http://localhost:3001" + "url": "http://localhost:5001" }, { "name": "Next.js: debug client-side (api)", "type": "chrome", "request": "launch", - "url": "http://localhost:3002" + "url": "http://localhost:5002" }, { "name": "Next.js: debug client-side (email)", "type": "chrome", "request": "launch", - "url": "http://localhost:3003" + "url": "http://localhost:5003" }, { "name": "Next.js: debug client-side (app)", "type": "chrome", "request": "launch", - "url": "http://localhost:3004" + "url": "http://localhost:5004" }, { "name": "Next.js: debug full stack", diff --git a/Cargo.lock b/Cargo.lock index 829e444..590de9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -246,6 +246,15 @@ dependencies = [ "quick-xml 0.37.1", ] +[[package]] +name = "atomic" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994" +dependencies = [ + "bytemuck", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -1362,6 +1371,30 @@ dependencies = [ "syn 2.0.92", ] +[[package]] +name = "dlsignal" +version = "0.1.0" +dependencies = [ + "async-trait", + "bytes", + "chrono", + "eyre", + "futures", + "itertools 0.13.0", + "lazy_static", + "librqbit-core", + "qbit-rs", + "quirks_path", + "regex", + "reqwest", + "serde", + "testcontainers", + "testcontainers-modules", + "thiserror 2.0.9", + "tokio", + "url", +] + [[package]] name = "docker_credential" version = "1.3.1" @@ -1536,6 +1569,22 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "figment" +version = "0.10.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3" +dependencies = [ + "atomic", + "pear", + "serde", + "serde_json", + "serde_yaml", + "toml", + "uncased", + "version_check", +] + [[package]] name = "filetime" version = "0.2.25" @@ -2431,6 +2480,12 @@ dependencies = [ "syn 2.0.92", ] +[[package]] +name = "inlinable_string" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" + [[package]] name = "insta" version = "1.41.1" @@ -3636,6 +3691,29 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" +[[package]] +name = "pear" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdeeaa00ce488657faba8ebf44ab9361f9365a97bd39ffb8a60663f57ff4b467" +dependencies = [ + "inlinable_string", + "pear_codegen", + "yansi", +] + +[[package]] +name = "pear_codegen" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bab5b985dc082b345f812b7df84e1bef27e7207b39e448439ba8bd69c93f147" +dependencies = [ + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.92", +] + [[package]] name = "pem" version = "3.0.4" @@ -4180,8 +4258,10 @@ dependencies = [ "axum-auth", "bytes", "chrono", + "dlsignal", "eyre", "fancy-regex", + "figment", "html-escape", "insta", "itertools 0.13.0", @@ -4209,7 +4289,6 @@ dependencies = [ "serial_test", "thiserror 2.0.9", "tokio", - "torrent", "tracing", "tracing-subscriber", "url", @@ -6125,30 +6204,6 @@ dependencies = [ "winnow", ] -[[package]] -name = "torrent" -version = "0.1.0" -dependencies = [ - "async-trait", - "bytes", - "chrono", - "eyre", - "futures", - "itertools 0.13.0", - "lazy_static", - "librqbit-core", - "qbit-rs", - "quirks_path", - "regex", - "reqwest", - "serde", - "testcontainers", - "testcontainers-modules", - "thiserror 2.0.9", - "tokio", - "url", -] - [[package]] name = "tower" version = "0.4.13" @@ -6376,6 +6431,15 @@ dependencies = [ "web-time", ] +[[package]] +name = "uncased" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" +dependencies = [ + "version_check", +] + [[package]] name = "unic-char-property" version = "0.9.0" diff --git a/Cargo.toml b/Cargo.toml index 429c0db..9492ba0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,3 @@ [workspace] -members = ["apps/recorder", "packages/quirks-path", "packages/torrent"] +members = ["apps/recorder", "packages/quirks-path", "packages/dlsignal"] resolver = "2" diff --git a/apps/api/.env.development b/apps/api/.env.development deleted file mode 100644 index a5eebbc..0000000 --- a/apps/api/.env.development +++ /dev/null @@ -1,14 +0,0 @@ -# Server -DATABASE_URL="postgres://konobangu:konobangu@127.0.0.1:5432/konobangu" -BETTERSTACK_API_KEY="" -BETTERSTACK_URL="" -FLAGS_SECRET="" -ARCJET_KEY="" -SVIX_TOKEN="" -LIVEBLOCKS_SECRET="" - -# Client -NEXT_PUBLIC_APP_URL="http://localhost:3000" -NEXT_PUBLIC_WEB_URL="http://localhost:3001" -NEXT_PUBLIC_DOCS_URL="http://localhost:3004" -NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL="https://webui.konobangu.com" \ No newline at end of file diff --git a/apps/api/.env.example b/apps/api/.env.example deleted file mode 100644 index ca7d1ee..0000000 --- a/apps/api/.env.example +++ /dev/null @@ -1,15 +0,0 @@ -# Server -BETTER_AUTH_SECRET="" -DATABASE_URL="" -BETTERSTACK_API_KEY="" -BETTERSTACK_URL="" -FLAGS_SECRET="" -ARCJET_KEY="" -SVIX_TOKEN="" -LIVEBLOCKS_SECRET="" - -# Client -NEXT_PUBLIC_APP_URL="http://localhost:3000" -NEXT_PUBLIC_WEB_URL="http://localhost:3001" -NEXT_PUBLIC_DOCS_URL="http://localhost:3004" -NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL="http://localhost:3000" \ No newline at end of file diff --git a/apps/api/.gitignore b/apps/api/.gitignore deleted file mode 100644 index 663a9cc..0000000 --- a/apps/api/.gitignore +++ /dev/null @@ -1,45 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js - -# testing -/coverage - -# next.js -/.next/ -/out/ - -# production -/build - -# misc -.DS_Store -*.pem - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* -.pnpm-debug.log* - -# local env files -.env*.local - -# vercel -.vercel - -# typescript -*.tsbuildinfo -next-env.d.ts - -# prisma -.env - -# react.email -.react-email - -# Sentry -.sentryclirc \ No newline at end of file diff --git a/apps/api/app/apple-icon.png b/apps/api/app/apple-icon.png deleted file mode 100644 index d185929cc52a90e65492c5e3ee719b48f1775c2a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 216 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvG8AvYpRA>UEBm#UwTz~!g_3G8DIdkUJ*46^W z^!4>`-@dJ&prHG8njVl}?djqeQo;E4yd&pf2LXlytxors@>OyD>S)ekadG82VlOC= zr}(D)*g+O!py}mT>@I#ebxSVRdzu+51hlq`pn|?Ne;2>3P0Q@%Yqf@|L8V#)e;;K5 b8pi5<*lSI@#dl|*GZ;Kw{an^LB{Ts5y-Go@ diff --git a/apps/api/app/global-error.tsx b/apps/api/app/global-error.tsx deleted file mode 100644 index 6e0511e..0000000 --- a/apps/api/app/global-error.tsx +++ /dev/null @@ -1,29 +0,0 @@ -'use client'; - -import { Button } from '@konobangu/design-system/components/ui/button'; -import { fonts } from '@konobangu/design-system/lib/fonts'; -import { captureException } from '@sentry/nextjs'; -import type NextError from 'next/error'; -import { useEffect } from 'react'; - -type GlobalErrorProperties = { - readonly error: NextError & { digest?: string }; - readonly reset: () => void; -}; - -const GlobalError = ({ error, reset }: GlobalErrorProperties) => { - useEffect(() => { - captureException(error); - }, [error]); - - return ( - - -

Oops, something went wrong

- - - - ); -}; - -export default GlobalError; diff --git a/apps/api/app/icon.png b/apps/api/app/icon.png deleted file mode 100644 index d79828515a04ae36e18cadffe4892aa5b5df2331..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 96 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4U>Ygr+Ar-fh6EYHh^ow6J7GPTJ t)+1$@b>xFVlF1IYUJ28rD;zTf8B!|$3GCaY_7kXu!PC{xWt~$(699t!99;ka diff --git a/apps/api/app/layout.tsx b/apps/api/app/layout.tsx deleted file mode 100644 index 23be9db..0000000 --- a/apps/api/app/layout.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import type { ReactNode } from 'react'; - -type RootLayoutProperties = { - readonly children: ReactNode; -}; - -const RootLayout = ({ children }: RootLayoutProperties) => ( - - {children} - -); - -export default RootLayout; diff --git a/apps/api/app/opengraph-image.png b/apps/api/app/opengraph-image.png deleted file mode 100644 index c79169f1eb2db58b5b35e1688f3149fba622d167..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 58106 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

ywa8VA#Nc>TQ}epi}OZq-5)L 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 &AppAuthService { - &AppAuthService::app_instance() + AppAuthService::app_instance() } } diff --git a/apps/recorder/src/auth/oidc.rs b/apps/recorder/src/auth/oidc.rs index 7e48c39..e1661ff 100644 --- a/apps/recorder/src/auth/oidc.rs +++ b/apps/recorder/src/auth/oidc.rs @@ -93,7 +93,7 @@ impl AuthService for OidcAuthService { let token_data = self.authorizer.check_auth(&token).await?; let claims = token_data.claims; - if !claims.sub.as_deref().is_some_and(|s| !s.trim().is_empty()) { + if claims.sub.as_deref().is_none_or(|s| s.trim().is_empty()) { return Err(AuthError::OidcSubMissingError); } if !claims.contains_audience(&config.audience) { @@ -103,7 +103,7 @@ impl AuthService for OidcAuthService { let found_scopes = claims.scopes().collect::>(); if !expected_scopes .iter() - .all(|es| found_scopes.contains(&es as &str)) + .all(|es| found_scopes.contains(es as &str)) { return Err(AuthError::OidcExtraScopesMatchError { expected: expected_scopes.iter().join(","), diff --git a/apps/recorder/src/auth/service.rs b/apps/recorder/src/auth/service.rs index a2395f1..2d61d9f 100644 --- a/apps/recorder/src/auth/service.rs +++ b/apps/recorder/src/auth/service.rs @@ -107,7 +107,7 @@ impl Initializer for AppAuthServiceInitializer { let service = AppAuthService::from_conf(auth_conf) .await - .map_err(|e| loco_rs::Error::wrap(e))?; + .map_err(loco_rs::Error::wrap)?; APP_AUTH_SERVICE.get_or_init(|| service); diff --git a/apps/recorder/src/config/mod.rs b/apps/recorder/src/config/mod.rs index cd3dbb3..08f8e72 100644 --- a/apps/recorder/src/config/mod.rs +++ b/apps/recorder/src/config/mod.rs @@ -1,12 +1,18 @@ +use figment::{ + providers::{Format, Json, Yaml}, + Figment, +}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use crate::{auth::AppAuthConfig, dal::config::AppDalConfig, extract::mikan::AppMikanConfig}; +const DEFAULT_APP_SETTINGS_MIXIN: &str = include_str!("./settings_mixin.yaml"); + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct AppConfig { pub auth: AppAuthConfig, - pub dal: Option, - pub mikan: Option, + pub dal: AppDalConfig, + pub mikan: AppMikanConfig, } pub fn deserialize_key_path_from_json_value( @@ -42,10 +48,19 @@ pub trait AppConfigExt { fn get_root_conf(&self) -> &loco_rs::config::Config; fn get_app_conf(&self) -> loco_rs::Result { - Ok( - deserialize_key_path_from_app_config(self.get_root_conf(), &[])? - .expect("app config must be present"), - ) + let settings_str = self + .get_root_conf() + .settings + .as_ref() + .map(serde_json::to_string) + .unwrap_or_else(|| Ok(String::new()))?; + + let app_config = Figment::from(Json::string(&settings_str)) + .merge(Yaml::string(DEFAULT_APP_SETTINGS_MIXIN)) + .extract() + .map_err(loco_rs::Error::wrap)?; + + Ok(app_config) } } diff --git a/apps/recorder/src/config/settings_mixin.yaml b/apps/recorder/src/config/settings_mixin.yaml new file mode 100644 index 0000000..e294736 --- /dev/null +++ b/apps/recorder/src/config/settings_mixin.yaml @@ -0,0 +1,12 @@ +dal: + data_dir: ./data + +mikan: + http_client: + exponential_backoff_max_retries: 3 + leaky_bucket_max_tokens: 3 + leaky_bucket_initial_tokens: 0 + leaky_bucket_refill_tokens: 1 + leaky_bucket_refill_interval: 500 + user_agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0" + base_url: "https://mikanani.me/" diff --git a/apps/recorder/src/dal/client.rs b/apps/recorder/src/dal/client.rs index ecd27d6..4faabee 100644 --- a/apps/recorder/src/dal/client.rs +++ b/apps/recorder/src/dal/client.rs @@ -194,7 +194,7 @@ impl Initializer for AppDalInitalizer { let config = &app_context.config; let app_dal_conf = config.get_app_conf()?.dal; - APP_DAL_CLIENT.get_or_init(|| AppDalClient::new(app_dal_conf.unwrap_or_default())); + APP_DAL_CLIENT.get_or_init(|| AppDalClient::new(app_dal_conf)); Ok(()) } diff --git a/apps/recorder/src/extract/mikan/client.rs b/apps/recorder/src/extract/mikan/client.rs index 5c5521e..5ae9ee4 100644 --- a/apps/recorder/src/extract/mikan/client.rs +++ b/apps/recorder/src/extract/mikan/client.rs @@ -3,7 +3,7 @@ use std::ops::Deref; use loco_rs::app::{AppContext, Initializer}; use once_cell::sync::OnceCell; -use super::{AppMikanConfig, MIKAN_BASE_URL}; +use super::AppMikanConfig; use crate::{config::AppConfigExt, fetch::HttpClient}; static APP_MIKAN_CLIENT: OnceCell = OnceCell::new(); @@ -14,12 +14,10 @@ pub struct AppMikanClient { } impl AppMikanClient { - pub fn new(mut config: AppMikanConfig) -> loco_rs::Result { + pub fn new(config: AppMikanConfig) -> loco_rs::Result { let http_client = - HttpClient::new(config.http_client.take()).map_err(loco_rs::Error::wrap)?; - let base_url = config - .base_url - .unwrap_or_else(|| String::from(MIKAN_BASE_URL)); + HttpClient::from_config(config.http_client).map_err(loco_rs::Error::wrap)?; + let base_url = config.base_url; Ok(Self { http_client, base_url, @@ -55,7 +53,7 @@ impl Initializer for AppMikanClientInitializer { async fn before_run(&self, app_context: &AppContext) -> loco_rs::Result<()> { let config = &app_context.config; - let app_mikan_conf = config.get_app_conf()?.mikan.unwrap_or_default(); + let app_mikan_conf = config.get_app_conf()?.mikan; APP_MIKAN_CLIENT.get_or_try_init(|| AppMikanClient::new(app_mikan_conf))?; diff --git a/apps/recorder/src/extract/mikan/config.rs b/apps/recorder/src/extract/mikan/config.rs index 8335cd2..228df62 100644 --- a/apps/recorder/src/extract/mikan/config.rs +++ b/apps/recorder/src/extract/mikan/config.rs @@ -2,8 +2,8 @@ use serde::{Deserialize, Serialize}; use crate::fetch::HttpClientConfig; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct AppMikanConfig { - pub http_client: Option, - pub base_url: Option, + pub http_client: HttpClientConfig, + pub base_url: String, } diff --git a/apps/recorder/src/extract/mikan/rss_parser.rs b/apps/recorder/src/extract/mikan/rss_parser.rs index 85dcad4..0123ad1 100644 --- a/apps/recorder/src/extract/mikan/rss_parser.rs +++ b/apps/recorder/src/extract/mikan/rss_parser.rs @@ -1,17 +1,17 @@ use std::ops::Deref; use chrono::DateTime; +use dlsignal::core::BITTORRENT_MIME_TYPE; use itertools::Itertools; use reqwest::IntoUrl; use serde::{Deserialize, Serialize}; -use torrent::core::BITTORRENT_MIME_TYPE; use url::Url; use super::{ web_parser::{parse_mikan_episode_id_from_homepage, MikanEpisodeHomepage}, AppMikanClient, }; -use crate::{extract::errors::ParseError, fetch::bytes::download_bytes_with_client}; +use crate::{extract::errors::ParseError, fetch::bytes::fetch_bytes}; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct MikanRssItem { @@ -228,7 +228,7 @@ pub async fn parse_mikan_rss_channel_from_rss_link( url: impl IntoUrl, ) -> eyre::Result { let http_client = client.map(|s| s.deref()); - let bytes = download_bytes_with_client(http_client, url.as_str()).await?; + let bytes = fetch_bytes(http_client, url.as_str()).await?; let channel = rss::Channel::read_from(&bytes[..])?; @@ -297,7 +297,7 @@ pub async fn parse_mikan_rss_channel_from_rss_link( mod tests { use std::assert_matches::assert_matches; - use torrent::core::BITTORRENT_MIME_TYPE; + use dlsignal::core::BITTORRENT_MIME_TYPE; use crate::extract::mikan::{ parse_mikan_rss_channel_from_rss_link, MikanBangumiAggregationRssChannel, diff --git a/apps/recorder/src/extract/mikan/web_parser.rs b/apps/recorder/src/extract/mikan/web_parser.rs index e0c8102..a61c54a 100644 --- a/apps/recorder/src/extract/mikan/web_parser.rs +++ b/apps/recorder/src/extract/mikan/web_parser.rs @@ -18,7 +18,7 @@ use crate::{ app::AppContextExt, dal::DalContentCategory, extract::html::parse_style_attr, - fetch::{html::download_html_with_client, image::download_image_with_client}, + fetch::{html::fetch_html, image::fetch_image}, models::subscribers, }; @@ -95,7 +95,7 @@ pub async fn parse_mikan_bangumi_poster_from_origin_poster_src( origin_poster_src: Url, ) -> eyre::Result { let http_client = client.map(|s| s.deref()); - let poster_data = download_image_with_client(http_client, origin_poster_src.clone()).await?; + let poster_data = fetch_image(http_client, origin_poster_src.clone()).await?; Ok(MikanBangumiPosterMeta { origin_poster_src, poster_data: Some(poster_data), @@ -127,8 +127,7 @@ pub async fn parse_mikan_bangumi_poster_from_origin_poster_src_with_cache( }); } - let poster_data = - download_image_with_client(Some(mikan_client.deref()), origin_poster_src.clone()).await?; + let poster_data = fetch_image(Some(mikan_client.deref()), origin_poster_src.clone()).await?; let poster_str = dal_client .store_object( @@ -153,7 +152,7 @@ pub async fn parse_mikan_bangumi_meta_from_mikan_homepage( ) -> eyre::Result { let http_client = client.map(|s| s.deref()); let url_host = url.origin().unicode_serialization(); - let content = download_html_with_client(http_client, url.as_str()).await?; + let content = fetch_html(http_client, url.as_str()).await?; let html = Html::parse_document(&content); let bangumi_fansubs = html @@ -276,7 +275,7 @@ pub async fn parse_mikan_episode_meta_from_mikan_homepage( ) -> eyre::Result { let http_client = client.map(|s| s.deref()); let url_host = url.origin().unicode_serialization(); - let content = download_html_with_client(http_client, url.as_str()).await?; + let content = fetch_html(http_client, url.as_str()).await?; let html = Html::parse_document(&content); @@ -401,6 +400,8 @@ pub async fn parse_mikan_episode_meta_from_mikan_homepage( }) } +pub async fn parse_mikan_bangumis_from_user_home(_client: Option<&AppMikanClient>, _url: Url) {} + #[cfg(test)] mod test { use std::assert_matches::assert_matches; diff --git a/apps/recorder/src/fetch/bytes.rs b/apps/recorder/src/fetch/bytes.rs index cee96bb..a0a71ab 100644 --- a/apps/recorder/src/fetch/bytes.rs +++ b/apps/recorder/src/fetch/bytes.rs @@ -1,24 +1,11 @@ use bytes::Bytes; use reqwest::IntoUrl; -use super::{core::DEFAULT_HTTP_CLIENT_USER_AGENT, HttpClient}; +use super::HttpClient; -pub async fn download_bytes(url: T) -> eyre::Result { - let request_client = reqwest::Client::builder() - .user_agent(DEFAULT_HTTP_CLIENT_USER_AGENT) - .build()?; - let bytes = request_client.get(url).send().await?.bytes().await?; +pub async fn fetch_bytes(client: Option<&HttpClient>, url: T) -> eyre::Result { + let client = client.unwrap_or_default(); + + let bytes = client.get(url).send().await?.bytes().await?; Ok(bytes) } - -pub async fn download_bytes_with_client( - client: Option<&HttpClient>, - url: T, -) -> eyre::Result { - if let Some(client) = client { - let bytes = client.get(url).send().await?.bytes().await?; - Ok(bytes) - } else { - download_bytes(url).await - } -} diff --git a/apps/recorder/src/fetch/client.rs b/apps/recorder/src/fetch/client.rs index 6007975..51031a5 100644 --- a/apps/recorder/src/fetch/client.rs +++ b/apps/recorder/src/fetch/client.rs @@ -2,6 +2,7 @@ use std::{ops::Deref, time::Duration}; use axum::http::Extensions; use leaky_bucket::RateLimiter; +use once_cell::sync::OnceCell; use reqwest::{ClientBuilder, Request, Response}; use reqwest_middleware::{ ClientBuilder as ClientWithMiddlewareBuilder, ClientWithMiddleware, Next, @@ -11,7 +12,7 @@ use reqwest_tracing::TracingMiddleware; use serde::{Deserialize, Serialize}; use serde_with::serde_as; -use super::DEFAULT_HTTP_CLIENT_USER_AGENT; +use crate::fetch::DEFAULT_HTTP_CLIENT_USER_AGENT; #[serde_as] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] @@ -27,6 +28,7 @@ pub struct HttpClientConfig { pub struct HttpClient { client: ClientWithMiddleware, + pub config: HttpClientConfig, } impl Deref for HttpClient { @@ -55,42 +57,73 @@ impl reqwest_middleware::Middleware for RateLimiterMiddleware { } impl HttpClient { - pub fn new(config: Option) -> reqwest::Result { - let mut config = config.unwrap_or_default(); - let retry_policy = ExponentialBackoff::builder() - .build_with_max_retries(config.exponential_backoff_max_retries.take().unwrap_or(3)); - let rate_limiter = RateLimiter::builder() - .max(config.leaky_bucket_max_tokens.take().unwrap_or(3) as usize) - .initial( - config - .leaky_bucket_initial_tokens - .take() - .unwrap_or_default() as usize, - ) - .refill(config.leaky_bucket_refill_tokens.take().unwrap_or(1) as usize) - .interval( - config - .leaky_bucket_refill_interval - .take() - .unwrap_or_else(|| Duration::from_millis(500)), - ) - .build(); + pub fn from_config(config: HttpClientConfig) -> reqwest::Result { + let reqwest_client_builder = ClientBuilder::new().user_agent( + config + .user_agent + .as_deref() + .unwrap_or(DEFAULT_HTTP_CLIENT_USER_AGENT), + ); - let client = ClientBuilder::new() - .user_agent( - config - .user_agent - .take() - .unwrap_or_else(|| DEFAULT_HTTP_CLIENT_USER_AGENT.to_owned()), - ) - .build()?; + let reqwest_client = reqwest_client_builder.build()?; + + let mut reqwest_with_middleware_builder = + ClientWithMiddlewareBuilder::new(reqwest_client).with(TracingMiddleware::default()); + + if let Some(ref x) = config.exponential_backoff_max_retries { + let retry_policy = ExponentialBackoff::builder().build_with_max_retries(*x); + + reqwest_with_middleware_builder = reqwest_with_middleware_builder + .with(RetryTransientMiddleware::new_with_policy(retry_policy)); + } + + if let (None, None, None, None) = ( + config.leaky_bucket_initial_tokens.as_ref(), + config.leaky_bucket_refill_tokens.as_ref(), + config.leaky_bucket_refill_interval.as_ref(), + config.leaky_bucket_max_tokens.as_ref(), + ) { + } else { + let mut rate_limiter_builder = RateLimiter::builder(); + + if let Some(ref x) = config.leaky_bucket_max_tokens { + rate_limiter_builder.max(*x as usize); + } + if let Some(ref x) = config.leaky_bucket_initial_tokens { + rate_limiter_builder.initial(*x as usize); + } + if let Some(ref x) = config.leaky_bucket_refill_tokens { + rate_limiter_builder.refill(*x as usize); + } + if let Some(ref x) = config.leaky_bucket_refill_interval { + rate_limiter_builder.interval(*x); + } + + let rate_limiter = rate_limiter_builder.build(); + + reqwest_with_middleware_builder = + reqwest_with_middleware_builder.with(RateLimiterMiddleware { rate_limiter }); + } + + let reqwest_with_middleware = reqwest_with_middleware_builder.build(); Ok(Self { - client: ClientWithMiddlewareBuilder::new(client) - .with(TracingMiddleware::default()) - .with(RateLimiterMiddleware { rate_limiter }) - .with(RetryTransientMiddleware::new_with_policy(retry_policy)) - .build(), + client: reqwest_with_middleware, + config, }) } } + +static DEFAULT_HTTP_CLIENT: OnceCell = OnceCell::new(); + +impl Default for HttpClient { + fn default() -> Self { + HttpClient::from_config(Default::default()).expect("Failed to create default HttpClient") + } +} + +impl Default for &HttpClient { + fn default() -> Self { + DEFAULT_HTTP_CLIENT.get_or_init(HttpClient::default) + } +} diff --git a/apps/recorder/src/fetch/html.rs b/apps/recorder/src/fetch/html.rs index ab6ce9f..2fe4442 100644 --- a/apps/recorder/src/fetch/html.rs +++ b/apps/recorder/src/fetch/html.rs @@ -1,23 +1,10 @@ use reqwest::IntoUrl; -use super::{core::DEFAULT_HTTP_CLIENT_USER_AGENT, HttpClient}; +use super::HttpClient; + +pub async fn fetch_html(client: Option<&HttpClient>, url: T) -> eyre::Result { + let client = client.unwrap_or_default(); + let content = client.get(url).send().await?.text().await?; -pub async fn download_html(url: U) -> eyre::Result { - let request_client = reqwest::Client::builder() - .user_agent(DEFAULT_HTTP_CLIENT_USER_AGENT) - .build()?; - let content = request_client.get(url).send().await?.text().await?; Ok(content) } - -pub async fn download_html_with_client( - client: Option<&HttpClient>, - url: T, -) -> eyre::Result { - if let Some(client) = client { - let content = client.get(url).send().await?.text().await?; - Ok(content) - } else { - download_html(url).await - } -} diff --git a/apps/recorder/src/fetch/image.rs b/apps/recorder/src/fetch/image.rs index 454f57d..1b5bb0e 100644 --- a/apps/recorder/src/fetch/image.rs +++ b/apps/recorder/src/fetch/image.rs @@ -1,18 +1,8 @@ use bytes::Bytes; use reqwest::IntoUrl; -use super::{ - bytes::{download_bytes, download_bytes_with_client}, - HttpClient, -}; +use super::{bytes::fetch_bytes, HttpClient}; -pub async fn download_image(url: U) -> eyre::Result { - download_bytes(url).await -} - -pub async fn download_image_with_client( - client: Option<&HttpClient>, - url: T, -) -> eyre::Result { - download_bytes_with_client(client, url).await +pub async fn fetch_image(client: Option<&HttpClient>, url: T) -> eyre::Result { + fetch_bytes(client, url).await } diff --git a/apps/recorder/src/fetch/mod.rs b/apps/recorder/src/fetch/mod.rs index 1c110a5..79bcc23 100644 --- a/apps/recorder/src/fetch/mod.rs +++ b/apps/recorder/src/fetch/mod.rs @@ -6,6 +6,7 @@ pub mod image; pub use core::DEFAULT_HTTP_CLIENT_USER_AGENT; -pub use bytes::download_bytes; +pub use bytes::fetch_bytes; pub use client::{HttpClient, HttpClientConfig}; -pub use image::download_image; +pub use html::fetch_html; +pub use image::fetch_image; diff --git a/apps/storybook/README.md b/apps/storybook/README.md index ef0e47e..a5cbf96 100644 --- a/apps/storybook/README.md +++ b/apps/storybook/README.md @@ -14,11 +14,11 @@ pnpm dev bun dev ``` -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. +Open [http://localhost:5000](http://localhost:5000) with your browser to see the result. You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file. -[API routes](https://nextjs.org/docs/pages/building-your-application/routing/api-routes) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`. +[API routes](https://nextjs.org/docs/pages/building-your-application/routing/api-routes) can be accessed on [http://localhost:5000/api/hello](http://localhost:5000/api/hello). This endpoint can be edited in `pages/api/hello.ts`. The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/pages/building-your-application/routing/api-routes) instead of React pages. diff --git a/apps/web/.env.development b/apps/web/.env.development index 52a6723..a1fee8a 100644 --- a/apps/web/.env.development +++ b/apps/web/.env.development @@ -9,7 +9,7 @@ SVIX_TOKEN="" LIVEBLOCKS_SECRET="" # Client -NEXT_PUBLIC_APP_URL="http://localhost:3000" -NEXT_PUBLIC_WEB_URL="http://localhost:3001" -NEXT_PUBLIC_DOCS_URL="http://localhost:3004" -NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL="https://webui.konobangu.com" \ No newline at end of file +NEXT_PUBLIC_APP_URL="http://localhost:5000" +NEXT_PUBLIC_WEB_URL="http://localhost:5001" +NEXT_PUBLIC_DOCS_URL="http://localhost:5004" +NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL="https://konobangu.com" \ No newline at end of file diff --git a/apps/web/.env.example b/apps/web/.env.example index ca7d1ee..ffce2a3 100644 --- a/apps/web/.env.example +++ b/apps/web/.env.example @@ -9,7 +9,7 @@ SVIX_TOKEN="" LIVEBLOCKS_SECRET="" # Client -NEXT_PUBLIC_APP_URL="http://localhost:3000" -NEXT_PUBLIC_WEB_URL="http://localhost:3001" -NEXT_PUBLIC_DOCS_URL="http://localhost:3004" -NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL="http://localhost:3000" \ No newline at end of file +NEXT_PUBLIC_APP_URL="http://localhost:5000" +NEXT_PUBLIC_WEB_URL="http://localhost:5001" +NEXT_PUBLIC_DOCS_URL="http://localhost:5004" +NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL="https://konobangu.com" \ No newline at end of file diff --git a/apps/web/package.json b/apps/web/package.json index cb86be1..eec5cca 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -2,7 +2,7 @@ "name": "web", "private": true, "scripts": { - "dev": "next dev -p 3001 --turbopack", + "dev": "next dev -p 5001 --turbopack", "build": "next build", "start": "next start", "analyze": "ANALYZE=true pnpm build", diff --git a/config/test.yaml b/config/test.yaml index 3d1fcf4..ca7528b 100644 --- a/config/test.yaml +++ b/config/test.yaml @@ -17,7 +17,7 @@ logger: # Web server configuration server: # Port on which the server will listen. the server binding is 0.0.0.0:{PORT} - port: 3001 + port: 5001 # The UI hostname or IP address that mailers will point to. host: http://webui.konobangu.com # Out of the box middleware configuration. to disable middleware you can changed the `enable` field to `false` of comment the middleware block diff --git a/docker-compose.yaml b/docker-compose.yaml index 4af7834..c5f4998 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -4,7 +4,7 @@ services: webui: image: node:22-alpine ports: - - '3000:3000' + - '5000:5000' volumes: - ./apps/webui:/home/node/app - node_modules:/home/node/app/node_modules diff --git a/packages/torrent/.gitignore b/packages/dlsignal/.gitignore similarity index 100% rename from packages/torrent/.gitignore rename to packages/dlsignal/.gitignore diff --git a/packages/torrent/Cargo.toml b/packages/dlsignal/Cargo.toml similarity index 95% rename from packages/torrent/Cargo.toml rename to packages/dlsignal/Cargo.toml index 1af0185..bd3f3e1 100644 --- a/packages/torrent/Cargo.toml +++ b/packages/dlsignal/Cargo.toml @@ -1,11 +1,11 @@ [package] -name = "torrent" +name = "dlsignal" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] -name = "torrent" +name = "dlsignal" path = "src/lib.rs" [features] diff --git a/packages/torrent/src/core.rs b/packages/dlsignal/src/core.rs similarity index 100% rename from packages/torrent/src/core.rs rename to packages/dlsignal/src/core.rs diff --git a/packages/torrent/src/error.rs b/packages/dlsignal/src/error.rs similarity index 100% rename from packages/torrent/src/error.rs rename to packages/dlsignal/src/error.rs diff --git a/packages/torrent/src/lib.rs b/packages/dlsignal/src/lib.rs similarity index 100% rename from packages/torrent/src/lib.rs rename to packages/dlsignal/src/lib.rs diff --git a/packages/torrent/src/qbit.rs b/packages/dlsignal/src/qbit.rs similarity index 100% rename from packages/torrent/src/qbit.rs rename to packages/dlsignal/src/qbit.rs diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 769b8ed..a5a5e55 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -49,70 +49,6 @@ importers: specifier: ^4.1.12 version: 4.1.14 - apps/api: - dependencies: - '@konobangu/analytics': - specifier: workspace:* - version: link:../../packages/analytics - '@konobangu/auth': - specifier: workspace:* - version: link:../../packages/auth - '@konobangu/database': - specifier: workspace:* - version: link:../../packages/database - '@konobangu/design-system': - specifier: workspace:* - version: link:../../packages/design-system - '@konobangu/env': - specifier: workspace:* - version: link:../../packages/env - '@konobangu/next-config': - specifier: workspace:* - version: link:../../packages/next-config - '@konobangu/observability': - specifier: workspace:* - version: link:../../packages/observability - '@sentry/nextjs': - specifier: ^8.43.0 - version: 8.47.0(@opentelemetry/core@1.30.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.56.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.0(@opentelemetry/api@1.9.0))(next@15.1.3(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.77.4))(react@19.0.0)(webpack@5.97.1) - import-in-the-middle: - specifier: ^1.11.3 - version: 1.12.0 - next: - specifier: ^15.1.3 - version: 15.1.3(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.77.4) - react: - specifier: ^19.0.0 - version: 19.0.0 - react-dom: - specifier: ^19.0.0 - version: 19.0.0(react@19.0.0) - require-in-the-middle: - specifier: ^7.4.0 - version: 7.4.0 - svix: - specifier: ^1.43.0 - version: 1.44.0 - devDependencies: - '@konobangu/typescript-config': - specifier: workspace:* - version: link:../../packages/typescript-config - '@types/node': - specifier: 22.10.1 - version: 22.10.1 - '@types/react': - specifier: 19.0.1 - version: 19.0.1 - '@types/react-dom': - specifier: 19.0.2 - version: 19.0.2(@types/react@19.0.1) - concurrently: - specifier: ^9.1.0 - version: 9.1.1 - typescript: - specifier: ^5.7.2 - version: 5.7.2 - apps/app: dependencies: '@konobangu/analytics': @@ -5476,10 +5412,6 @@ packages: cliui@6.0.0: resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} - cliui@8.0.1: - resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} - engines: {node: '>=12'} - clone@1.0.4: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} engines: {node: '>=0.8'} @@ -5586,11 +5518,6 @@ packages: resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==} engines: {'0': node >= 0.8} - concurrently@9.1.1: - resolution: {integrity: sha512-6VX8lrBIycgZKTwBsWS+bLrmkGRkDmvtGsYylRN9b93CygN6CbK46HmnQ3rdSOR8HRjdahDrxb5MqD9cEFOg5Q==} - engines: {node: '>=18'} - hasBin: true - console-browserify@1.2.0: resolution: {integrity: sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==} @@ -9090,10 +9017,6 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - shell-quote@1.8.2: - resolution: {integrity: sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==} - engines: {node: '>= 0.4'} - shelljs@0.8.5: resolution: {integrity: sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==} engines: {node: '>=4'} @@ -9596,10 +9519,6 @@ packages: resolution: {integrity: sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==} engines: {node: '>=18'} - tree-kill@1.2.2: - resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} - hasBin: true - trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} @@ -10221,10 +10140,6 @@ packages: y18n@4.0.3: resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} - y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} - yallist@2.1.2: resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==} @@ -10244,18 +10159,10 @@ packages: resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} engines: {node: '>=6'} - yargs-parser@21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} - yargs@15.4.1: resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} engines: {node: '>=8'} - yargs@17.7.2: - resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} - engines: {node: '>=12'} - yn@3.1.1: resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} engines: {node: '>=6'} @@ -15191,12 +15098,6 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 6.2.0 - cliui@8.0.1: - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 7.0.0 - clone@1.0.4: {} clone@2.1.2: {} @@ -15289,16 +15190,6 @@ snapshots: readable-stream: 2.3.8 typedarray: 0.0.6 - concurrently@9.1.1: - dependencies: - chalk: 4.1.2 - lodash: 4.17.21 - rxjs: 7.8.1 - shell-quote: 1.8.2 - supports-color: 8.1.1 - tree-kill: 1.2.2 - yargs: 17.7.2 - console-browserify@1.2.0: {} constant-case@2.0.0: @@ -19605,8 +19496,6 @@ snapshots: shebang-regex@3.0.0: {} - shell-quote@1.8.2: {} - shelljs@0.8.5: dependencies: glob: 7.2.3 @@ -20266,8 +20155,6 @@ snapshots: dependencies: punycode: 2.3.1 - tree-kill@1.2.2: {} - trim-lines@3.0.1: {} trough@2.2.0: {} @@ -21029,8 +20916,6 @@ snapshots: y18n@4.0.3: {} - y18n@5.0.8: {} - yallist@2.1.2: {} yallist@3.1.1: {} @@ -21044,8 +20929,6 @@ snapshots: camelcase: 5.3.1 decamelize: 1.2.0 - yargs-parser@21.1.1: {} - yargs@15.4.1: dependencies: cliui: 6.0.0 @@ -21060,16 +20943,6 @@ snapshots: y18n: 4.0.3 yargs-parser: 18.1.3 - yargs@17.7.2: - dependencies: - cliui: 8.0.1 - escalade: 3.2.0 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - string-width: 4.2.3 - y18n: 5.0.8 - yargs-parser: 21.1.1 - yn@3.1.1: {} yocto-queue@0.1.0: {}