From 0fb201792410d77e47cd06ea2c41be42f598182b Mon Sep 17 00:00:00 2001 From: LynithDev <61880709+LynithDev@users.noreply.github.com> Date: Fri, 20 Sep 2024 22:07:02 +0200 Subject: [PATCH] feat: Start working on implementing Curseforge into the frontend + fetch_categories helper script --- .template.env | 3 + Cargo.lock | 9 +- Cargo.toml | 2 +- apps/frontend/src/ui/hooks/useBrowser.tsx | 4 +- .../src/ui/pages/browser/BrowserRoot.tsx | 2 +- .../src/ui/pages/browser/BrowserSearch.tsx | 2 +- apps/frontend/src/utils/browser.ts | 139 ++++++++++-------- apps/frontend/src/utils/index.ts | 5 +- packages/core/.template.env | 2 - packages/core/build.rs | 42 +++++- .../src/api/package/content/curseforge.rs | 52 +++---- packages/scripts/api/fetch_categories.ts | 120 +++++++++++++++ packages/scripts/package.json | 5 +- pnpm-lock.yaml | 29 ++++ 14 files changed, 309 insertions(+), 107 deletions(-) create mode 100644 .template.env delete mode 100644 packages/core/.template.env create mode 100644 packages/scripts/api/fetch_categories.ts diff --git a/.template.env b/.template.env new file mode 100644 index 00000000..434d6c54 --- /dev/null +++ b/.template.env @@ -0,0 +1,3 @@ +#! Use single quotes due to special characters and dotenvy being weird + +CURSEFORGE_API_KEY = '' diff --git a/Cargo.lock b/Cargo.lock index 56fd1d0d..9a172198 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1672,6 +1672,11 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "git+https://github.com/allan2/dotenvy/?rev=db0d6aa8fc8d1b0ddbfe94b5c833372e463eb1c5#db0d6aa8fc8d1b0ddbfe94b5c833372e463eb1c5" + [[package]] name = "dpi" version = "0.1.1" @@ -4357,7 +4362,7 @@ dependencies = [ "chrono", "dirs 5.0.1", "discord-rich-presence", - "dotenvy", + "dotenvy 0.15.7 (git+https://github.com/allan2/dotenvy/?rev=db0d6aa8fc8d1b0ddbfe94b5c833372e463eb1c5)", "flate2", "futures", "indicatif", @@ -5153,7 +5158,7 @@ dependencies = [ "bigdecimal 0.4.5", "chrono", "diagnostics", - "dotenvy", + "dotenvy 0.15.7 (registry+https://github.com/rust-lang/crates.io-index)", "futures", "include_dir", "indexmap 1.9.3", diff --git a/Cargo.toml b/Cargo.toml index 5f78aeda..8276853b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -179,7 +179,7 @@ cocoa = { version = "0.26" } objc = { version = "0.2" } cc = { version = "1.1" } libc = { version = "0.2" } -dotenvy = { version = "0.15.7" } +dotenvy = { git = "https://github.com/allan2/dotenvy/", rev = "db0d6aa8fc8d1b0ddbfe94b5c833372e463eb1c5" } # https://github.com/tauri-apps/tauri/blob/dev/crates/tauri/Cargo.toml#L102 webkit2gtk = { version = "=2.0.1", features = [ "v2_40" ] } diff --git a/apps/frontend/src/ui/hooks/useBrowser.tsx b/apps/frontend/src/ui/hooks/useBrowser.tsx index 5de57a25..473ca0e5 100644 --- a/apps/frontend/src/ui/hooks/useBrowser.tsx +++ b/apps/frontend/src/ui/hooks/useBrowser.tsx @@ -1,5 +1,3 @@ -import type { Cluster, ManagedPackage, PackageType, Providers, ProviderSearchQuery, ProviderSearchResults } from '@onelauncher/client/bindings'; -import type { ModalProps } from '~ui/components/overlay/Modal'; import { useNavigate } from '@solidjs/router'; import { bridge } from '~imports'; import Button from '~ui/components/base/Button'; @@ -7,6 +5,8 @@ import Dropdown from '~ui/components/base/Dropdown'; import Modal, { createModal } from '~ui/components/overlay/Modal'; import BrowserPackage from '~ui/pages/browser/BrowserPackage'; import { type Accessor, type Context, createContext, createEffect, createSignal, For, on, onMount, type ParentProps, type Setter, Show, untrack, useContext } from 'solid-js'; +import type { Cluster, ManagedPackage, PackageType, Providers, ProviderSearchQuery, ProviderSearchResults } from '@onelauncher/client/bindings'; +import type { ModalProps } from '~ui/components/overlay/Modal'; import { useRecentCluster } from './useCluster'; import useCommand, { tryResult } from './useCommand'; diff --git a/apps/frontend/src/ui/pages/browser/BrowserRoot.tsx b/apps/frontend/src/ui/pages/browser/BrowserRoot.tsx index d4896d9e..c2f8a713 100644 --- a/apps/frontend/src/ui/pages/browser/BrowserRoot.tsx +++ b/apps/frontend/src/ui/pages/browser/BrowserRoot.tsx @@ -93,7 +93,7 @@ function BrowserCategories() {
Categories
- + {category => (

bridge.commands.searchProviderPackages('Modrinth', browser.searchQuery())); + const [results, { refetch }] = useCommand(() => bridge.commands.searchProviderPackages(browser.searchQuery().provider, browser.searchQuery())); const paginationOptions = (): PaginationOptions => ({ itemsCount: () => results()?.total || 0, diff --git a/apps/frontend/src/utils/browser.ts b/apps/frontend/src/utils/browser.ts index cba8f15f..dabfc757 100644 --- a/apps/frontend/src/utils/browser.ts +++ b/apps/frontend/src/utils/browser.ts @@ -7,74 +7,11 @@ export interface CategoryItem { id: string; } -type ModCategories = [ - 'adventure', - 'library', - 'equipment', - 'decoration', - 'food', - 'magic', - 'performance', - 'storage', - 'technology', - 'utility', - 'worldgen', -]; - -const modMapping: Record = { - Modrinth: { - adventure: { - display: 'Adventure', - id: 'adventure', - }, - library: { - display: 'Library', - id: 'library', - }, - decoration: { - display: 'Decoration', - id: 'decoration', - }, - equipment: { - display: 'Equipment', - id: 'equipment', - }, - food: { - display: 'Food', - id: 'food', - }, - magic: { - display: 'Magic', - id: 'magic', - }, - performance: { - display: 'Performance', - id: 'optimization', - }, - storage: { - display: 'Storage', - id: 'storage', - }, - technology: { - display: 'Technology', - id: 'technology', - }, - utility: { - display: 'Utility', - id: 'utility', - }, - worldgen: { - display: 'World Generation', - id: 'worldgen', - }, - }, -}; - export const browserCategories = { - byPackageType(packageType: PackageType): CategoryItem[] { + byPackageType(packageType: PackageType, provider: Providers = 'Modrinth'): CategoryItem[] { switch (packageType) { case 'mod': - return this.mod(); + return this.mod(provider); } return []; @@ -84,3 +21,75 @@ export const browserCategories = { return Object.values(modMapping[provider]); }, }; + +const modMapping: Record = { + Curseforge: [ + { display: 'Server Utility', id: '435' }, + { display: 'Armor, Tools, and Weapons', id: '434' }, + { display: 'API and Library', id: '421' }, + { display: 'Adventure and RPG', id: '422' }, + { display: 'World Gen', id: '406' }, + { display: 'Buildcraft', id: '432' }, + { display: 'Twitch Integration', id: '4671' }, + { display: 'Industrial Craft', id: '429' }, + { display: 'Magic', id: '419' }, + { display: 'Cosmetic', id: '424' }, + { display: 'KubeJS', id: '5314' }, + { display: 'Storage', id: '420' }, + { display: 'Mobs', id: '411' }, + { display: 'Ores and Resources', id: '408' }, + { display: 'Create', id: '6484' }, + { display: 'Genetics', id: '418' }, + { display: 'Food', id: '436' }, + { display: 'Automation', id: '4843' }, + { display: 'Performance', id: '6814' }, + { display: 'Farming', id: '416' }, + { display: 'CraftTweaker', id: '4773' }, + { display: 'Structures', id: '409' }, + { display: 'Map and Information', id: '423' }, + { display: 'Bug Fixes', id: '6821' }, + { display: 'Biomes', id: '407' }, + { display: 'Energy', id: '417' }, + { display: 'Addons', id: '426' }, + { display: 'Blood Magic', id: '4485' }, + { display: 'Energy, Fluid, and Item Transport', id: '415' }, + { display: 'Forestry', id: '433' }, + { display: 'Education', id: '5299' }, + { display: 'Technology', id: '412' }, + { display: 'Thermal Expansion', id: '427' }, + { display: 'Dimensions', id: '410' }, + { display: 'Thaumcraft', id: '430' }, + { display: 'Processing', id: '413' }, + { display: 'Tinker\'s Construct', id: '428' }, + { display: 'Miscellaneous', id: '425' }, + { display: 'Skyblock', id: '6145' }, + { display: 'Galacticraft', id: '5232' }, + { display: 'Player Transport', id: '414' }, + { display: 'Applied Energistics 2', id: '4545' }, + { display: 'Integrated Dynamics', id: '6954' }, + { display: 'Redstone', id: '4558' }, + { display: 'Utility & QoL', id: '5191' }, + { display: 'MCreator', id: '4906' }, + ], + Modrinth: [ + { display: 'Adventure', id: 'adventure' }, + { display: 'Cursed', id: 'cursed' }, + { display: 'Decoration', id: 'decoration' }, + { display: 'Economy', id: 'economy' }, + { display: 'Equipment', id: 'equipment' }, + { display: 'Food', id: 'food' }, + { display: 'Game Mechanics', id: 'game-mechanics' }, + { display: 'Library', id: 'library' }, + { display: 'Magic', id: 'magic' }, + { display: 'Management', id: 'management' }, + { display: 'Minigame', id: 'minigame' }, + { display: 'Mobs', id: 'mobs' }, + { display: 'Optimization', id: 'optimization' }, + { display: 'Social', id: 'social' }, + { display: 'Storage', id: 'storage' }, + { display: 'Technology', id: 'technology' }, + { display: 'Transportation', id: 'transportation' }, + { display: 'Utility', id: 'utility' }, + { display: 'World Generation', id: 'worldgen' }, + ], +}; diff --git a/apps/frontend/src/utils/index.ts b/apps/frontend/src/utils/index.ts index 932db3e7..b70ba771 100644 --- a/apps/frontend/src/utils/index.ts +++ b/apps/frontend/src/utils/index.ts @@ -1,6 +1,6 @@ -import type { Cluster, ImportType, License, Loader, PackageType, Providers, VersionType } from '@onelauncher/client/bindings'; import { DurationFormat } from '@formatjs/intl-durationformat'; import { open } from '@tauri-apps/plugin-shell'; +import type { Cluster, ImportType, License, Loader, PackageType, Providers, VersionType } from '@onelauncher/client/bindings'; export function setAsyncTimeout(ms: number): Promise; export function setAsyncTimeout(callback: () => void, ms: number): Promise; @@ -163,12 +163,13 @@ export function getLicenseUrl(licenseId: string | License | null | undefined): s export function getPackageUrl(provider: Providers, id: string, package_type: PackageType = 'mod'): string { const mapping: Record = { Modrinth: `https://modrinth.com/${package_type}/${id}`, + Curseforge: `https://www.curseforge.com/minecraft/${package_type}/${id}`, }; return mapping[provider]; } export const LOADERS: Loader[] = ['vanilla', 'fabric', 'forge', 'neoforge', 'quilt'] as const; -export const PROVIDERS: Providers[] = ['Modrinth'] as const; +export const PROVIDERS: Providers[] = ['Modrinth', 'Curseforge'] as const; export const PACKAGE_TYPES: PackageType[] = ['mod', 'resourcepack', 'datapack', 'shaderpack'] as const; export const LAUNCHER_IMPORT_TYPES: ImportType[] = ['PrismLauncher', 'Curseforge', 'Modrinth', 'ATLauncher', 'GDLauncher', 'FTBLauncher', 'MultiMC', 'TLauncher', 'Technic'] as const; diff --git a/packages/core/.template.env b/packages/core/.template.env deleted file mode 100644 index 1fe71cf6..00000000 --- a/packages/core/.template.env +++ /dev/null @@ -1,2 +0,0 @@ -# You may need to escape any special characters such as $ -CURSEFORGE_API_KEY = "" diff --git a/packages/core/build.rs b/packages/core/build.rs index 4d667801..e57d8343 100644 --- a/packages/core/build.rs +++ b/packages/core/build.rs @@ -1,9 +1,41 @@ -fn main() { - let dotenv_path = dotenvy::dotenv().expect("failed to find .env file"); - println!("cargo:rerun-if-changed={}", dotenv_path.display()); +use std::path::PathBuf; - for env_var in dotenvy::dotenv_iter().unwrap() { - let (key, value) = env_var.unwrap(); +fn main() { + let paths = vec![ + get_root_workspace().join(".env"), + PathBuf::from(".env"), + ]; + + for path in paths { + if path.exists() { + load_from_path(path); + } + } +} + +fn get_root_workspace() -> PathBuf { + let output: Vec = std::process::Command::new(env!("CARGO")) + .arg("locate-project") + .arg("--workspace") + .arg("--message-format=plain") + .output() + .unwrap() + .stdout; + + let root_workspace = String::from_utf8(output).unwrap(); + let mut root_workspace = PathBuf::from(root_workspace.trim()); + + if root_workspace.is_file() { + root_workspace.pop(); + } + + root_workspace +} + +fn load_from_path(path: PathBuf) { + let vars = dotenvy::EnvLoader::with_path(path).load().expect("failed to find .env file"); + + for (key, value) in vars { println!("cargo:rustc-env={key}={value}"); } } \ No newline at end of file diff --git a/packages/core/src/api/package/content/curseforge.rs b/packages/core/src/api/package/content/curseforge.rs index d3f1c2ad..43ea08d1 100644 --- a/packages/core/src/api/package/content/curseforge.rs +++ b/packages/core/src/api/package/content/curseforge.rs @@ -71,6 +71,7 @@ impl From for CurseforgeLoader { #[derive(Deserialize)] #[serde(rename_all = "camelCase")] +#[allow(dead_code)] pub struct Pagination { pub index: u32, pub page_size: u32, @@ -89,25 +90,25 @@ pub struct CurseforgePackage { pub slug: String, pub links: Links, pub summary: String, - pub status: u32, + // pub status: u32, pub download_count: u64, pub is_featured: bool, - pub primary_category_id: u32, + // pub primary_category_id: u32, pub categories: Vec, pub authors: Vec, pub logo: Logo, - pub screenshots: Vec, - pub main_file_id: u32, - pub latest_files: Vec, - pub latest_files_indexes: Vec, - pub latest_early_access_files_indexes: Vec, + // pub screenshots: Vec, + // pub main_file_id: u32, + // pub latest_files: Vec, + // pub latest_files_indexes: Vec, + // pub latest_early_access_files_indexes: Vec, pub date_created: DateTime, pub date_modified: DateTime, pub date_released: DateTime, #[serde(default)] pub allow_mod_distribution: bool, - pub game_popularity_rank: u32, - pub is_available: bool, + // pub game_popularity_rank: u32, + // pub is_available: bool, pub thumbs_up_count: u32, pub rating: Option, } @@ -183,7 +184,7 @@ pub struct Category { pub url: String, pub icon_url: String, pub date_modified: String, - pub parent_category_id: u32, + // pub parent_category_id: u32, // pub display_index: u32, } @@ -226,11 +227,11 @@ pub struct LatestFile { pub is_available: bool, pub display_name: String, pub file_name: String, - pub release_type: u32, - pub file_status: u32, - pub hashes: Vec, - pub file_date: String, - pub file_length: u32, + // pub release_type: u32, + // pub file_status: u32, + // pub hashes: Vec, + // pub file_date: String, + // pub file_length: u32, pub download_count: u32, // pub file_size_on_disk: u32, pub download_url: Option, @@ -319,7 +320,7 @@ struct CFData { data: T, } -pub async fn fetch(url: &str, url_builder: Option>) -> Result { +pub async fn fetch_url_builder(url: &str, url_builder: Option) -> Result { // TODO: Get the API key from settings, fallback to constant, and error if missing let key = crate::constants::CURSEFORGE_API_KEY.ok_or(anyhow::anyhow!("missing curseforge api key"))?; @@ -338,7 +339,7 @@ pub async fn fetch(url: &str, url_builder: Option>) -> http::fetch_advanced( Method::GET, - url.as_str(), + dbg!(url.as_str()), None, None, Some(headers), @@ -348,6 +349,10 @@ pub async fn fetch(url: &str, url_builder: Option>) -> .await } +async fn fetch(url: &str) -> Result { + fetch_url_builder(url, None::).await +} + pub async fn search( query: Option, limit: Option, @@ -364,7 +369,7 @@ pub async fn search( } let results: CFSearchResults = serde_json::from_slice( - &fetch( + &fetch_url_builder( "/v1/mods/search", Some(Box::new(move |url: &mut Url| { let params = &mut url.query_pairs_mut(); @@ -378,16 +383,16 @@ pub async fn search( if let Some(game_versions) = game_versions { // Need to create a list e.g. ["1.16.5", "1.17.1"] - params.append_pair("gameVersions", game_versions.into_iter().map(|v| format!("\"{}\"", v)).collect::>().join(",").as_str()); + params.append_pair("gameVersions", format!("[{}]", game_versions.into_iter().map(|v| format!("\"{}\"", v)).collect::>().join(",")).as_str()); } if let Some(loaders) = loaders { - params.append_pair("modLoaderTypes", loaders.into_iter().map(|l| (CurseforgeLoader::from(l) as u32).to_string()).collect::>().join(",").as_str()); + params.append_pair("modLoaderTypes", format!("[{}]", loaders.into_iter().map(|l| (CurseforgeLoader::from(l) as u32).to_string()).collect::>().join(",")).as_str()); } // TODO: Merge this with categories if let Some(package_types) = package_types { - params.append_pair("categoryIds", package_types.into_iter().map(|p| (CFPackageType::from(p) as u32).to_string()).collect::>().join(",").as_str()); + params.append_pair("categoryIds", format!("[{}]", package_types.into_iter().map(|p| (CFPackageType::from(p) as u32).to_string()).collect::>().join(",")).as_str()); } })) ).await?)?; @@ -401,10 +406,7 @@ pub async fn search( pub async fn get(id: u32) -> Result { Ok(serde_json::from_slice::>( - &fetch( - format!("/v1/mods/{}", id).as_str(), - None, - ) + &fetch(format!("/v1/mods/{}", id).as_str()) .await?, )?.data) } \ No newline at end of file diff --git a/packages/scripts/api/fetch_categories.ts b/packages/scripts/api/fetch_categories.ts new file mode 100644 index 00000000..5cd6ef5d --- /dev/null +++ b/packages/scripts/api/fetch_categories.ts @@ -0,0 +1,120 @@ +import process from 'node:process'; +import { findWorkspaceDir } from '@pnpm/find-workspace-dir'; +import dotenv from 'dotenv'; + +interface MappedCategoryItem { + display: string; + id: string; +} + +type MappingList = Record; + +const packageTypes = ['mod', 'modpack', 'shader', 'resourcepack'] as const; +type PackageType = typeof packageTypes[number]; + +async function loadEnv() { + const path = await findWorkspaceDir(process.cwd()); + console.log(`Loading .env from ${path}`); + dotenv.config({ path: `${path}/.env` }); +} + +async function fetchCategoriesCurseforge(packageType: PackageType): Promise { + const key = process.env.CURSEFORGE_API_KEY; + + if (key === undefined) { + console.error('CURSEFORGE_API_KEY is not defined'); + return {}; + } + + const response = await fetch('https://api.curseforge.com/v1/categories?gameId=432', { + headers: { + 'x-api-key': key, + }, + }); + + interface CFCategory { + id: number; + name: 'Server Utility'; + slug: 'server-utility'; + url: 'https://www.curseforge.com/minecraft/mc-mods/server-utility'; + iconUrl: 'https://media.forgecdn.net/avatars/6/48/635351498950580836.png'; + classId?: number; + isClass?: boolean; + parentCategoryId?: number; + }; + + const data = (await response.json() as { data: CFCategory[] }).data; + + // TODO: Add more calssIds + const classIds: Record = { + mod: 6, + resourcepack: 12, + modpack: 4471, + shader: 6552, + }; + + const mapping: MappingList = {}; + + mapping.Curseforge = data + .filter((category) => { + return category.isClass !== true && category.classId === classIds[packageType]; + }) + .map((category) => { + return { + display: category.name, + id: category.id.toString(), + }; + }); + + return mapping; +} + +async function fetchCategoriesModrinth(packageType: PackageType): Promise { + const response = await fetch('https://api.modrinth.com/v2/tag/category'); + + interface ModrinthCategory { + icon: string; + name: string; + project_type: PackageType; + header: string; + } + + const data = await response.json() as ModrinthCategory[]; + + const mapping: MappingList = {}; + + mapping.Modrinth = data + .filter((category) => { + return category.project_type === packageType; + }) + .map((category) => { + return { + display: category.name.charAt(0).toUpperCase() + category.name.slice(1), + id: category.name, + }; + }); + + return mapping; +} + +(async () => { + const arg = process.argv[2]; + if (arg === undefined) { + console.error(`Please provide an argument for package type.\n Valid: '${packageTypes.join('\', \'')}'`); + return; + } + + const packageType = arg as PackageType; + + await loadEnv(); + + const cfCategories = await fetchCategoriesCurseforge(packageType); + const modrinthCategories = await fetchCategoriesModrinth(packageType); + + const categories = { + ...cfCategories, + ...modrinthCategories, + }; + + console.log(categories); +})(); diff --git a/packages/scripts/package.json b/packages/scripts/package.json index fc77e68b..3caaca7e 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -22,10 +22,13 @@ "setup-nix": "./setup.sh", "autoformat": "./format.sh", "fix-deb": "./fix-deb.sh", - "dupe": "./dedupe.sh" + "dupe": "./dedupe.sh", + "api:categories": "pnpm tsx ./api/fetch_categories.ts" }, "dependencies": { + "@pnpm/find-workspace-dir": "^7.0.1", "consola": "^3.2.3", + "dotenv": "^16.4.5", "execa": "^9.4.0", "mustache": "^4.2.0", "pathe": "^1.1.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3ee1a8e3..6c090203 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -264,9 +264,15 @@ importers: packages/scripts: dependencies: + '@pnpm/find-workspace-dir': + specifier: ^7.0.1 + version: 7.0.1 consola: specifier: ^3.2.3 version: 3.2.3 + dotenv: + specifier: ^16.4.5 + version: 16.4.5 execa: specifier: ^9.4.0 version: 9.4.0 @@ -1020,6 +1026,18 @@ packages: resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@pnpm/constants@8.0.0': + resolution: {integrity: sha512-yQosGUvYPpAjb1jOFcdbwekRjZRVxN6C0hHzfRCZrMKbxGjt/E0g0RcFlEDNVZ95tm4oMMcr7nEPa7H7LX3emw==} + engines: {node: '>=18.12'} + + '@pnpm/error@6.0.1': + resolution: {integrity: sha512-7yjO0RgmWYb4OKgcWC33yD4Z2CxE7Tm7vXX1SmS7GDifDT/bgZZhHeS2xq/+W6y9yhwIrRSA+7AlQL1NM2wIvw==} + engines: {node: '>=18.12'} + + '@pnpm/find-workspace-dir@7.0.1': + resolution: {integrity: sha512-o1LAFM/5MChI6qBolMBOznzatch01UK3wIgoAE/b779qs1FakksB278nMRTwRY58PZSBT+RxZ2RCMjlxPLeVWw==} + engines: {node: '>=18.12'} + '@polka/url@1.0.0-next.25': resolution: {integrity: sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==} @@ -4866,6 +4884,17 @@ snapshots: '@pkgr/core@0.1.1': {} + '@pnpm/constants@8.0.0': {} + + '@pnpm/error@6.0.1': + dependencies: + '@pnpm/constants': 8.0.0 + + '@pnpm/find-workspace-dir@7.0.1': + dependencies: + '@pnpm/error': 6.0.1 + find-up: 5.0.0 + '@polka/url@1.0.0-next.25': {} '@prisma/debug@5.19.1': {}