Merge pull request #1 from Polyfrost/lynith
Support for Vanilla Minecraft + slight refactoring
This commit is contained in:
commit
e3de6705d8
43 changed files with 1274 additions and 947 deletions
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
12
.idea/Launcher.iml
generated
Normal file
12
.idea/Launcher.iml
generated
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="EMPTY_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/onelauncher/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src-tauri/src" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
7
.idea/discord.xml
generated
Normal file
7
.idea/discord.xml
generated
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DiscordProjectSettings">
|
||||
<option name="show" value="PROJECT_FILES" />
|
||||
<option name="description" value="" />
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
6
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/Launcher.iml" filepath="$PROJECT_DIR$/.idea/Launcher.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -32,7 +32,10 @@ tracing-subscriber = { version = "0.3", features = [
|
|||
tracing-error = { version = "0.2" }
|
||||
tracing-appender = { version = "0.2" }
|
||||
|
||||
uuid = { version = "1.8", features = [ "serde" ] }
|
||||
uuid = { version = "1.8", features = [
|
||||
"serde",
|
||||
"v4"
|
||||
] }
|
||||
tokio = { version = "1", features = [ "full" ] }
|
||||
serde = { version = "1", features = [ "derive" ] }
|
||||
chrono = { version = "0.4", features = [ "serde" ] }
|
||||
|
|
|
|||
|
|
@ -10,10 +10,6 @@ readme = { workspace = true }
|
|||
homepage = { workspace = true }
|
||||
authors = { workspace = true }
|
||||
|
||||
[features]
|
||||
tauri = [ "dep:tauri" ]
|
||||
cli = [ "dep:indicatif" ]
|
||||
|
||||
[dependencies]
|
||||
tauri = { workspace = true, optional = true }
|
||||
indicatif = { workspace = true, optional = true }
|
||||
|
|
@ -41,11 +37,3 @@ dirs = { workspace = true }
|
|||
open = { workspace = true }
|
||||
sha1 = { workspace = true }
|
||||
discord-rich-presence = { workspace = true }
|
||||
notify = { workspace = true }
|
||||
notify-debouncer-mini = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
sysinfo = { workspace = true }
|
||||
sys-info = { workspace = true }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winreg = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ pub struct Account {
|
|||
pub username: String,
|
||||
pub uuid: String,
|
||||
pub skins: Vec<AccountSkin>,
|
||||
pub access_token: String, // TODO deal with this properly
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
|
|
|
|||
|
|
@ -2,16 +2,45 @@
|
|||
pub const NAME: &str = "OneLauncher";
|
||||
pub const VERSION: &str = "0.1.0";
|
||||
|
||||
|
||||
// Authentication and HTTP (TODO: env?)
|
||||
pub const APP_CONFIG_DIR: &str = "org.polyfrost.launcher";
|
||||
pub const USER_AGENT: &str = "OneLauncher/1.0.0 (polyfrost.org)";
|
||||
|
||||
|
||||
// Microsoft Authentication
|
||||
pub const CLIENT_ID: &str = "9419b7ee-1448-4d1b-b52a-550d8f36ab56";
|
||||
pub const MSA_PORT: u16 = 13523;
|
||||
|
||||
// Minecraft
|
||||
pub const MINECRAFT_VERSIONS_MANIFEST: &str =
|
||||
"https://piston-meta.mojang.com/mc/game/version_manifest_v2.json";
|
||||
|
||||
// Java Downloading
|
||||
// Minecraft
|
||||
pub const MINECRAFT_VERSIONS_MANIFEST: &str = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json";
|
||||
|
||||
|
||||
// OS constants which match Minecraft's/Mojang's scheme
|
||||
#[cfg(target_os = "windows")]
|
||||
pub const TARGET_OS: &str = "windows";
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub const TARGET_OS: &str = "osx";
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub const TARGET_OS: &str = "linux";
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
pub const NATIVE_ARCH: &str = "32";
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub const NATIVE_ARCH: &str = "64";
|
||||
|
||||
// TODO: Add more architectures
|
||||
#[cfg(all(not(target_arch = "x86_64"), not(target_arch = "x86")))]
|
||||
pub const NATIVE_ARCH: &str = "64";
|
||||
|
||||
|
||||
// Minecraft library splitter
|
||||
#[cfg(target_os = "windows")]
|
||||
pub const LIBRARY_SPLITTER: &str = ";";
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub const LIBRARY_SPLITTER: &str = ":";
|
||||
|
|
|
|||
|
|
@ -15,10 +15,6 @@ pub enum ErrorKind {
|
|||
#[error("tokio join handle error: {0}")]
|
||||
JoinError(#[from] tokio::task::JoinError),
|
||||
|
||||
/// Wrapper around [`notify::Error`] to handle file watching errors.
|
||||
#[error("failed to watch file: {0}")]
|
||||
NotifyError(#[from] notify::Error),
|
||||
|
||||
/// Wrapper around [`flate2::CompressError`] to handle flate compression errors.
|
||||
#[error("failed to compress a file with flate: {0}")]
|
||||
FlateError(#[from] flate2::CompressError),
|
||||
|
|
@ -73,11 +69,11 @@ pub enum ErrorKind {
|
|||
|
||||
/// Wrapper around [`crate::game::client::ClientManagerError`] to handle directory errors.
|
||||
#[error("failed to manage clients: {0}")]
|
||||
ClientManagerError(#[from] crate::game::client::ClientManagerError),
|
||||
ClientManagerError(#[from] crate::game::client_manager::ClientManagerError),
|
||||
|
||||
#[cfg(feature = "tauri")]
|
||||
#[error("failed to launch tauri: {0}")]
|
||||
TauriError(#[from] tauri::Error),
|
||||
/// Wrapper around [`reqwest::header::ToStrError`] to handle header conversion errors.
|
||||
#[error("failed to convert header to string: {0}")]
|
||||
HeaderToStrError(#[from] reqwest::header::ToStrError),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
|
|||
|
|
@ -1,38 +1,27 @@
|
|||
use std::{collections::HashMap, fs, path::PathBuf};
|
||||
use std::{path::PathBuf, process::Stdio};
|
||||
|
||||
use anyhow::anyhow;
|
||||
use async_trait::async_trait;
|
||||
use chrono::{DateTime, Local};
|
||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::{io::{AsyncBufReadExt, BufReader}, process};
|
||||
use tracing::info;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::utils::dirs;
|
||||
use crate::auth::Account;
|
||||
|
||||
use super::{
|
||||
minecraft::{MinecraftManifest, ReleaseType},
|
||||
vanilla::{self, VanillaClientProps},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Version {
|
||||
id: String,
|
||||
url: String,
|
||||
#[serde(default)]
|
||||
release_type: ReleaseType,
|
||||
#[serde(default)]
|
||||
release_time: chrono::DateTime<chrono::Utc>,
|
||||
}
|
||||
use super::{clients::ClientType, minecraft::{MinecraftManifest, ModernArgumentsItemExt}};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Manifest {
|
||||
pub id: Uuid,
|
||||
pub manifest: MinecraftManifest,
|
||||
#[serde(rename = "manifest")]
|
||||
pub minecraft_manifest: MinecraftManifest,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Instance {
|
||||
pub struct Cluster {
|
||||
pub id: Uuid,
|
||||
pub created_at: DateTime<Local>,
|
||||
pub name: String,
|
||||
|
|
@ -41,50 +30,120 @@ pub struct Instance {
|
|||
pub client: ClientType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum ClientType {
|
||||
Vanilla(VanillaClientProps),
|
||||
impl Cluster {
|
||||
pub fn dir(&self) -> crate::Result<PathBuf> {
|
||||
Ok(crate::utils::dirs::cluster_dir(self.id.to_string())?)
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientType {
|
||||
pub async fn get_versions(&self, file: Option<&PathBuf>) -> crate::Result<Vec<Version>> {
|
||||
match self {
|
||||
ClientType::Vanilla(_) => vanilla::get_versions(file).await,
|
||||
}
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct LaunchInfo {
|
||||
pub java: PathBuf,
|
||||
pub account: Account,
|
||||
pub mem_min: u32,
|
||||
pub mem_max: u32,
|
||||
pub setup: SetupInfo,
|
||||
}
|
||||
|
||||
pub fn get_impl<'a>(
|
||||
client: &'a ClientType,
|
||||
instance: &'a Instance,
|
||||
manifest: &'a Manifest,
|
||||
) -> Box<dyn ClientTrait<'a> + 'a> {
|
||||
Box::new(match client {
|
||||
ClientType::Vanilla(_) => vanilla::VanillaClient::new(instance, manifest),
|
||||
})
|
||||
#[derive(Debug)]
|
||||
pub struct SetupInfo {
|
||||
pub version: String,
|
||||
pub libraries: String,
|
||||
pub game_dir: PathBuf,
|
||||
pub natives_dir: PathBuf,
|
||||
pub assets_dir: PathBuf,
|
||||
pub asset_index: String,
|
||||
}
|
||||
|
||||
pub struct LaunchCallbacks {
|
||||
pub on_launch: Box<dyn FnMut(u32) + Send>,
|
||||
pub on_stdout: Box<dyn FnMut(String) + Send>,
|
||||
pub on_stderr: Box<dyn FnMut(String) + Send>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait ClientTrait<'a>: Send + Sync {
|
||||
fn new(instance: &'a Instance, manifest: &'a Manifest) -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
fn new(cluster: &'a Cluster, manifest: &'a Manifest) -> Self where Self: Sized;
|
||||
|
||||
async fn launch(&self) -> crate::Result<()>;
|
||||
async fn setup(&self) -> crate::Result<()>;
|
||||
async fn launch(&self, info: LaunchInfo, mut callbacks: LaunchCallbacks) -> crate::Result<i32> {
|
||||
let manifest = &self.get_manifest().minecraft_manifest;
|
||||
|
||||
async fn install_game(&self) -> crate::Result<PathBuf>;
|
||||
async fn install_libraries(&self) -> crate::Result<String>;
|
||||
async fn install_natives(&self) -> crate::Result<()>;
|
||||
async fn install_assets(&self) -> crate::Result<()>;
|
||||
let args = get_arguments(manifest)?
|
||||
.replace("${game_assets}", "${assets_root}")
|
||||
.replace("${auth_player_name}", &info.account.username)
|
||||
.replace("${version_name}", &info.setup.version)
|
||||
.replace("${game_directory}", info.setup.game_dir.to_str().unwrap())
|
||||
.replace("${assets_root}", info.setup.assets_dir.to_str().unwrap())
|
||||
.replace("${assets_index_name}", &manifest.asset_index.id)
|
||||
.replace("${auth_uuid}", &info.account.uuid)
|
||||
.replace("${auth_access_token}", &info.account.access_token)
|
||||
.replace("${auth_session}", "0") // TODO: Figure out how to get this session ID.
|
||||
.replace("${user_properties}", "{}") // TODO: Figure out these properties.
|
||||
.replace("${user_type}", "1")
|
||||
.replace("${version_type}", "OneLauncher");
|
||||
|
||||
fn get_instance(&self) -> &'a Instance
|
||||
where
|
||||
Self: Sized;
|
||||
fn get_manifest(&self) -> &'a Manifest
|
||||
where
|
||||
Self: Sized;
|
||||
let mut process = process::Command::new(&info.java).arg("-XX:-UseAdaptiveSizePolicy")
|
||||
.arg("-XX:-OmitStackTraceInFastThrow")
|
||||
.arg("-Dminecraft.launcher.brand=onelauncher")
|
||||
.arg("-Dminecraft.launcher.version=1")
|
||||
.arg(format!("-Djava.library.path={}", info.setup.natives_dir.to_str().unwrap()))
|
||||
.arg(format!("-Xms{}M", info.mem_min))
|
||||
.arg(format!("-Xmx{}M", info.mem_max))
|
||||
.arg("-cp")
|
||||
.arg(info.setup.libraries)
|
||||
.arg(manifest.main_class.clone())
|
||||
.args(args.split_whitespace())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?;
|
||||
|
||||
let pid = process.id().unwrap_or(0);
|
||||
(callbacks.on_launch)(pid);
|
||||
|
||||
let stdout = process.stdout.take().ok_or(anyhow!("Couldn't take stdout"))?;
|
||||
let stderr = process.stderr.take().ok_or(anyhow!("Couldn't take stderr"))?;
|
||||
|
||||
let mut stdout_lines = BufReader::new(stdout).lines();
|
||||
let mut stderr_lines = BufReader::new(stderr).lines();
|
||||
|
||||
let stdout_task = tokio::spawn(async move {
|
||||
while let Some(line) = stdout_lines.next_line().await.expect("Couldn't read line") {
|
||||
(callbacks.on_stdout)(line);
|
||||
}
|
||||
});
|
||||
|
||||
let stderr_task = tokio::spawn(async move {
|
||||
while let Some(line) = stderr_lines.next_line().await.expect("Couldn't read line") {
|
||||
(callbacks.on_stderr)(line);
|
||||
}
|
||||
});
|
||||
|
||||
let status = process.wait().await?;
|
||||
tokio::try_join!(stdout_task, stderr_task)?;
|
||||
|
||||
let exit_code = status.code().unwrap_or(0);
|
||||
Ok(exit_code)
|
||||
}
|
||||
|
||||
async fn setup(&self) -> crate::Result<SetupInfo>;
|
||||
|
||||
fn get_cluster(&self) -> &'a Cluster;
|
||||
|
||||
fn get_manifest(&self) -> &'a Manifest;
|
||||
}
|
||||
|
||||
pub fn get_arguments(manifest: &MinecraftManifest) -> crate::Result<String> {
|
||||
// Modern
|
||||
if let Some(arguments) = &manifest.arguments {
|
||||
return Ok(arguments.game.build());
|
||||
}
|
||||
|
||||
// Legacy
|
||||
if let Some(arguments) = &manifest.minecraft_arguments {
|
||||
return Ok(arguments.clone());
|
||||
}
|
||||
|
||||
Err(anyhow!("No arguments found").into())
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
|
|
@ -97,7 +156,7 @@ macro_rules! create_game_client {
|
|||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct $client<'a> {
|
||||
pub instance: &'a crate::game::client::Instance,
|
||||
pub cluster: &'a crate::game::client::Cluster,
|
||||
pub manifest: &'a crate::game::client::Manifest,
|
||||
$(pub $name: $type),*
|
||||
}
|
||||
|
|
@ -107,11 +166,11 @@ macro_rules! create_game_client {
|
|||
#[macro_export]
|
||||
macro_rules! impl_game_client {
|
||||
() => {
|
||||
fn get_instance(&self) -> &'a crate::game::client::Instance
|
||||
fn get_cluster(&self) -> &'a crate::game::client::Cluster
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.instance
|
||||
self.cluster
|
||||
}
|
||||
|
||||
fn get_manifest(&self) -> &'a crate::game::client::Manifest
|
||||
|
|
@ -121,184 +180,4 @@ macro_rules! impl_game_client {
|
|||
self.manifest
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ClientManagerError {
|
||||
#[error("Instance not found")]
|
||||
InstanceNotFound,
|
||||
#[error("Manifest not found")]
|
||||
ManifestNotFound,
|
||||
}
|
||||
|
||||
pub struct ClientManager {
|
||||
instances: HashMap<Uuid, Instance>,
|
||||
manifests: HashMap<Uuid, Manifest>,
|
||||
}
|
||||
|
||||
impl ClientManager {
|
||||
pub fn new() -> crate::Result<Self> {
|
||||
let instances = load_and_serialize::<Instance>(&dirs::instances_dir()?)?;
|
||||
|
||||
let manifests = load_and_serialize::<Manifest>(&dirs::manifests_dir()?)?;
|
||||
|
||||
Ok(Self {
|
||||
instances,
|
||||
manifests,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_impl_uuid<'a>(&'a self, uuid: Uuid) -> crate::Result<Box<dyn ClientTrait<'a> + 'a>> {
|
||||
let instance = self.get_instance(uuid)?;
|
||||
let manifest = self.get_manifest(uuid)?;
|
||||
|
||||
Ok(get_impl(&instance.client, &instance, &manifest))
|
||||
}
|
||||
|
||||
pub fn get_instances(&self) -> Vec<&Instance> {
|
||||
self.instances.values().collect()
|
||||
}
|
||||
|
||||
pub fn get_instances_owned(&self) -> Vec<Instance> {
|
||||
self.instances.values().cloned().collect()
|
||||
}
|
||||
|
||||
pub fn get_instance(&self, uuid: Uuid) -> crate::Result<&Instance> {
|
||||
self.instances
|
||||
.get(&uuid)
|
||||
.ok_or(ClientManagerError::InstanceNotFound.into())
|
||||
}
|
||||
|
||||
pub fn get_instance_mut(&mut self, uuid: Uuid) -> crate::Result<&mut Instance> {
|
||||
self.instances
|
||||
.get_mut(&uuid)
|
||||
.ok_or(ClientManagerError::InstanceNotFound.into())
|
||||
}
|
||||
|
||||
pub fn get_manifest(&self, uuid: Uuid) -> crate::Result<&Manifest> {
|
||||
self.manifests
|
||||
.get(&uuid)
|
||||
.ok_or(ClientManagerError::ManifestNotFound.into())
|
||||
}
|
||||
|
||||
pub fn get_manifest_mut(&mut self, uuid: Uuid) -> crate::Result<&mut Manifest> {
|
||||
self.manifests
|
||||
.get_mut(&uuid)
|
||||
.ok_or(ClientManagerError::ManifestNotFound.into())
|
||||
}
|
||||
|
||||
pub async fn create_instance(
|
||||
&mut self,
|
||||
name: String,
|
||||
version: String,
|
||||
cover: Option<String>,
|
||||
group: Option<Uuid>,
|
||||
client: ClientType,
|
||||
) -> crate::Result<Uuid> {
|
||||
let version_cache = dirs::app_config_dir()?.join("versions_cache.json");
|
||||
let versions = client.get_versions(Some(&version_cache)).await?;
|
||||
let url = &versions
|
||||
.iter()
|
||||
.find(|v| v.id == version)
|
||||
.ok_or(anyhow!("Version not found"))?
|
||||
.url;
|
||||
|
||||
let uuid = Uuid::new_v4();
|
||||
|
||||
// Save the manifest
|
||||
let minecraft_manifest = vanilla::retrieve_version_manifest(url).await?;
|
||||
let manifest = Manifest {
|
||||
id: uuid,
|
||||
manifest: minecraft_manifest,
|
||||
};
|
||||
|
||||
save(
|
||||
&dirs::manifests_dir()?.join(format!("{}.json", uuid)),
|
||||
&manifest,
|
||||
)?;
|
||||
self.manifests.insert(uuid, manifest);
|
||||
|
||||
// Save the instance
|
||||
let instance = Instance {
|
||||
id: uuid,
|
||||
created_at: Local::now(),
|
||||
name,
|
||||
cover,
|
||||
group,
|
||||
client,
|
||||
};
|
||||
|
||||
save(
|
||||
&dirs::instances_dir()?.join(format!("{}.json", uuid)),
|
||||
&instance,
|
||||
)?;
|
||||
self.instances.insert(uuid, instance);
|
||||
|
||||
Ok(uuid)
|
||||
}
|
||||
}
|
||||
|
||||
fn save<T>(path: &PathBuf, object: T) -> crate::Result<()>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
if !path.exists() {
|
||||
fs::create_dir_all(
|
||||
path.parent()
|
||||
.expect("Couldn't get parent of path whilst saving object. 'client.rs'"),
|
||||
)?;
|
||||
}
|
||||
|
||||
let content = serde_json::to_string(&object)?;
|
||||
fs::write(path, content)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load_and_serialize<T>(dir: &PathBuf) -> crate::Result<HashMap<Uuid, T>>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
let mut result = HashMap::<Uuid, T>::new();
|
||||
|
||||
if !dir.exists() {
|
||||
fs::create_dir_all(dir)?;
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
for file in fs::read_dir(dir)? {
|
||||
let file = file?;
|
||||
let file_name = file.file_name();
|
||||
let file_name = file_name
|
||||
.to_str()
|
||||
.ok_or(anyhow!("Couldn't convert OsStr to str"))?
|
||||
.replace(".json", "");
|
||||
let uuid = match Uuid::parse_str(&file_name) {
|
||||
Ok(uuid) => uuid,
|
||||
Err(_) => {
|
||||
eprintln!("Couldn't parse file name as UUID: '{:?}'", file_name);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let path = file.path();
|
||||
let content = match fs::read_to_string(&path) {
|
||||
Ok(content) => content,
|
||||
Err(_) => {
|
||||
eprintln!("Couldn't read file: '{:?}'", path);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let parsed = match serde_json::from_str::<T>(&content) {
|
||||
Ok(parsed) => parsed,
|
||||
Err(_) => {
|
||||
eprintln!("Couldn't parse file content as JSON: '{:?}'", content);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
result.insert(uuid, parsed);
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
193
onelauncher/src/game/client_manager.rs
Normal file
193
onelauncher/src/game/client_manager.rs
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
use std::{collections::HashMap, fs, path::PathBuf};
|
||||
|
||||
use anyhow::anyhow;
|
||||
use chrono::Local;
|
||||
use serde::Serialize;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{auth::Account, utils::dirs};
|
||||
|
||||
use super::{client::{ClientTrait, Cluster, LaunchCallbacks, LaunchInfo, Manifest}, clients::{self, vanilla, ClientType}, java};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ClientManagerError {
|
||||
#[error("Cluster not found")]
|
||||
ClusterNotFound,
|
||||
|
||||
#[error("Manifest not found")]
|
||||
ManifestNotFound,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct ClusterWithManifest {
|
||||
pub cluster: Cluster,
|
||||
pub manifest: Manifest
|
||||
}
|
||||
|
||||
pub struct ClientManager {
|
||||
clusters: HashMap<Uuid, ClusterWithManifest>,
|
||||
}
|
||||
|
||||
impl ClientManager {
|
||||
pub fn new() -> crate::Result<Self> {
|
||||
Ok(Self {
|
||||
clusters: load_and_serialize()?
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_impl_uuid<'a>(&'a self, uuid: Uuid) -> crate::Result<Box<dyn ClientTrait<'a> + 'a>> {
|
||||
let ClusterWithManifest { cluster, manifest } = self.get_cluster(uuid)?;
|
||||
|
||||
Ok(clients::get_impl(&cluster.client, cluster, manifest))
|
||||
}
|
||||
|
||||
pub fn get_clusters(&self) -> Vec<&ClusterWithManifest> {
|
||||
self.clusters.values().collect()
|
||||
}
|
||||
|
||||
pub fn get_clusters_owned(&self) -> Vec<ClusterWithManifest> {
|
||||
self.clusters.values().cloned().collect()
|
||||
}
|
||||
|
||||
pub fn get_cluster(&self, uuid: Uuid) -> crate::Result<&ClusterWithManifest> {
|
||||
self.clusters
|
||||
.get(&uuid)
|
||||
.ok_or(ClientManagerError::ClusterNotFound.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientManager {
|
||||
pub async fn launch_cluster(
|
||||
&mut self,
|
||||
uuid: Uuid,
|
||||
callbacks: LaunchCallbacks,
|
||||
) -> crate::Result<i32> {
|
||||
let client = self.get_impl_uuid(uuid)?;
|
||||
|
||||
let java = java::download_java(&dirs::java_dir()?, client.get_manifest().minecraft_manifest.java_version.major_version).await?;
|
||||
let setup = client.setup().await?;
|
||||
|
||||
// TODO: Make this configurable
|
||||
let info = LaunchInfo {
|
||||
java,
|
||||
setup,
|
||||
account: Account {
|
||||
username: "Player 0".to_string(),
|
||||
access_token: "0".to_string(),
|
||||
uuid: Uuid::new_v4().to_string(),
|
||||
skins: vec![]
|
||||
},
|
||||
mem_min: 1024,
|
||||
mem_max: 4096,
|
||||
};
|
||||
|
||||
let exit_code = client.launch(info, callbacks).await?;
|
||||
Ok(exit_code)
|
||||
}
|
||||
|
||||
pub async fn create_cluster(
|
||||
&mut self,
|
||||
name: String,
|
||||
version: String,
|
||||
cover: Option<String>,
|
||||
group: Option<Uuid>,
|
||||
client: ClientType,
|
||||
) -> crate::Result<Uuid> {
|
||||
let version_cache = dirs::app_config_dir()?.join("versions_cache.json");
|
||||
let versions = clients::get_versions(&client, Some(&version_cache)).await?;
|
||||
let url = &versions
|
||||
.iter()
|
||||
.find(|v| v.id == version)
|
||||
.ok_or(anyhow!("Version not found"))?
|
||||
.url;
|
||||
|
||||
let uuid = Uuid::new_v4();
|
||||
let dir = &dirs::cluster_dir(uuid.to_string())?;
|
||||
|
||||
// Save the manifest
|
||||
// TODO: Is this correct?
|
||||
let minecraft_manifest = vanilla::retrieve_version_manifest(url).await?;
|
||||
let manifest = Manifest {
|
||||
id: uuid,
|
||||
minecraft_manifest,
|
||||
};
|
||||
|
||||
save(
|
||||
&dir.join("manifest.json"),
|
||||
&manifest,
|
||||
)?;
|
||||
|
||||
// Save the cluster
|
||||
let cluster = Cluster {
|
||||
id: uuid,
|
||||
created_at: Local::now(),
|
||||
name,
|
||||
cover,
|
||||
group,
|
||||
client,
|
||||
};
|
||||
|
||||
save(
|
||||
&dir.join("cluster.json"),
|
||||
&cluster,
|
||||
)?;
|
||||
|
||||
self.clusters.insert(uuid, ClusterWithManifest { cluster, manifest });
|
||||
|
||||
Ok(uuid)
|
||||
}
|
||||
}
|
||||
|
||||
fn save<T>(path: &PathBuf, object: T) -> crate::Result<()>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
if !path.exists() {
|
||||
fs::create_dir_all(
|
||||
path.parent()
|
||||
.expect("Couldn't get parent of path whilst saving object. 'client.rs'"),
|
||||
)?;
|
||||
}
|
||||
|
||||
let content = serde_json::to_string(&object)?;
|
||||
fs::write(path, content)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load_and_serialize() -> crate::Result<HashMap<Uuid, ClusterWithManifest>> {
|
||||
let mut result = HashMap::<Uuid, ClusterWithManifest>::new();
|
||||
|
||||
let dir = dirs::clusters_dir()?;
|
||||
if !dir.exists() {
|
||||
fs::create_dir_all(&dir)?;
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
for entry in fs::read_dir(dir)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
|
||||
if path.is_dir() {
|
||||
match get_entry(&path) {
|
||||
Ok((uuid, cluster)) => {
|
||||
result.insert(uuid, cluster);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Failed to load cluster: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn get_entry(path: &PathBuf) -> crate::Result<(Uuid, ClusterWithManifest)> {
|
||||
let manifest_path = path.join("manifest.json");
|
||||
let cluster_path = path.join("cluster.json");
|
||||
|
||||
let manifest: Manifest = serde_json::from_str(&fs::read_to_string(manifest_path)?)?;
|
||||
let cluster: Cluster = serde_json::from_str(&fs::read_to_string(cluster_path)?)?;
|
||||
|
||||
Ok((cluster.id, ClusterWithManifest { cluster, manifest }))
|
||||
}
|
||||
27
onelauncher/src/game/clients/mod.rs
Normal file
27
onelauncher/src/game/clients/mod.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use self::vanilla::VanillaClientProps;
|
||||
|
||||
use super::{client::{ClientTrait, Cluster, Manifest}, minecraft::MinecraftVersion};
|
||||
|
||||
pub mod vanilla;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum ClientType {
|
||||
Vanilla(VanillaClientProps),
|
||||
}
|
||||
|
||||
pub async fn get_versions(client: &ClientType, file: Option<&PathBuf>) -> crate::Result<Vec<MinecraftVersion>> {
|
||||
match client {
|
||||
ClientType::Vanilla(_) => vanilla::get_versions(file).await,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_impl<'a>(client: &'a ClientType, cluster: &'a Cluster, manifest: &'a Manifest) -> Box<dyn ClientTrait<'a> + 'a> {
|
||||
Box::new(match client {
|
||||
ClientType::Vanilla(_) => vanilla::VanillaClient::new(cluster, manifest),
|
||||
})
|
||||
}
|
||||
241
onelauncher/src/game/clients/vanilla.rs
Normal file
241
onelauncher/src/game/clients/vanilla.rs
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
use crate::{
|
||||
constants::{self, MINECRAFT_VERSIONS_MANIFEST},
|
||||
create_game_client,
|
||||
game::{client::{ClientTrait, Cluster, Manifest, SetupInfo}, minecraft::{AssetIndexFile, Library, MinecraftManifest, MinecraftVersion, RuleListExt}},
|
||||
impl_game_client,
|
||||
utils::{dirs, file, http}
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use async_trait::async_trait;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{fs, path::PathBuf};
|
||||
|
||||
pub fn vanilla_dir() -> crate::Result<PathBuf> {
|
||||
Ok(dirs::clients_dir()?.join("vanilla"))
|
||||
}
|
||||
|
||||
create_game_client! {
|
||||
VanillaClientProps {}
|
||||
VanillaClient {}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<'a> ClientTrait<'a> for VanillaClient<'a> {
|
||||
impl_game_client!();
|
||||
|
||||
fn new(cluster: &'a Cluster, manifest: &'a Manifest) -> Self {
|
||||
VanillaClient { cluster, manifest }
|
||||
}
|
||||
|
||||
async fn setup(&self) -> crate::Result<SetupInfo> {
|
||||
let manifest = &self.manifest.minecraft_manifest;
|
||||
|
||||
// Create game directory
|
||||
let game_dir = self.cluster.dir()?.join("game");
|
||||
fs::create_dir_all(&game_dir)?;
|
||||
|
||||
// Install everything
|
||||
let client_path = install_game(manifest).await?;
|
||||
let mut libraries = install_libraries(manifest).await?;
|
||||
|
||||
// Resources dir used for legacy versions
|
||||
// with map_to_resources set to true in their asset indexes
|
||||
let resources_dir = game_dir.join("resources");
|
||||
let (asset_index, assets_dir) = install_assets(manifest, &resources_dir).await?;
|
||||
|
||||
// Append client path to the libraries string at the end
|
||||
libraries.push_str(constants::LIBRARY_SPLITTER);
|
||||
libraries.push_str(client_path.to_str().unwrap());
|
||||
|
||||
|
||||
Ok(SetupInfo {
|
||||
version: manifest.version.clone(),
|
||||
libraries,
|
||||
natives_dir: dirs::natives_dir()?,
|
||||
game_dir,
|
||||
assets_dir,
|
||||
asset_index,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn install_game(manifest: &MinecraftManifest) -> crate::Result<PathBuf> {
|
||||
let file_name = format!("{}.jar", manifest.version);
|
||||
let file = vanilla_dir()?.join(file_name);
|
||||
|
||||
if !file.exists() {
|
||||
fs::create_dir_all(&file.parent().ok_or(anyhow!("Couldn't take client jar parent folder"))?)?;
|
||||
|
||||
let artifact = &manifest.downloads.client;
|
||||
http::download_file_sha1_check(&artifact.url, &file, &artifact.sha1).await?;
|
||||
}
|
||||
|
||||
Ok(file)
|
||||
}
|
||||
|
||||
pub async fn install_libraries(manifest: &MinecraftManifest) -> crate::Result<String> {
|
||||
let libraries = &manifest.libraries;
|
||||
|
||||
let mut natives_ret: Vec<&Library> = vec![];
|
||||
let mut libraries_ret: Vec<String> = vec![];
|
||||
|
||||
for library in libraries {
|
||||
if !&library.rules.check() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(_) = library.natives {
|
||||
natives_ret.push(library);
|
||||
continue;
|
||||
}
|
||||
|
||||
let artifact = library.downloads.artifact.clone().ok_or(anyhow!("No artifact object"))?;
|
||||
let path = artifact.path;
|
||||
let url = artifact.url;
|
||||
|
||||
let dest = dirs::libraries_dir()?.join(path);
|
||||
fs::create_dir_all(dest.parent().ok_or(anyhow!("Couldn't get library parent"))?)?;
|
||||
|
||||
if !dest.exists() {
|
||||
http::download_file_sha1_check(url.as_str(), &dest, &artifact.sha1).await?;
|
||||
}
|
||||
|
||||
libraries_ret.push(dest.to_str().unwrap().to_string());
|
||||
}
|
||||
|
||||
install_natives(natives_ret).await?;
|
||||
Ok(libraries_ret.join(constants::LIBRARY_SPLITTER))
|
||||
}
|
||||
|
||||
pub async fn install_natives(natives: Vec<&Library>) -> crate::Result<()> {
|
||||
for native in natives {
|
||||
let classifiers = native.natives.as_ref().unwrap();
|
||||
let classifier = match classifiers.get(constants::TARGET_OS) {
|
||||
Some(classifier) => classifier.replace("${arch}", constants::NATIVE_ARCH),
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let artifact = native.downloads.classifiers.as_ref()
|
||||
.ok_or(anyhow!("No classifiers object"))?
|
||||
.get(&classifier)
|
||||
.ok_or(anyhow!("No classifier object"))?;
|
||||
|
||||
let path = artifact.path.clone();
|
||||
let url = artifact.url.clone();
|
||||
|
||||
let dest = dirs::libraries_dir()?.join(path);
|
||||
fs::create_dir_all(dest.parent().ok_or(anyhow!("Couldn't get native parent"))?)?;
|
||||
|
||||
if !dest.exists() {
|
||||
http::download_file_sha1_check(url.as_str(), &dest, &artifact.sha1).await?;
|
||||
file::extract_zip(&dest, &dirs::natives_dir()?)?; // TODO: Handle this properly
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn install_assets(manifest: &MinecraftManifest, resources_dir: &PathBuf) -> crate::Result<(String, PathBuf)> {
|
||||
let assets = dirs::assets_dir()?;
|
||||
let objects = assets.join("objects");
|
||||
let indexes = assets.join("indexes");
|
||||
let index = indexes.join(format!("{}.json", manifest.asset_index.id));
|
||||
|
||||
fs::create_dir_all(&objects)?;
|
||||
fs::create_dir_all(&indexes)?;
|
||||
|
||||
if !index.exists() {
|
||||
let artifact = &manifest.asset_index;
|
||||
http::download_file_sha1_check(&artifact.url, &index, &artifact.sha1).await?;
|
||||
}
|
||||
|
||||
let contents = serde_json::from_str::<AssetIndexFile>(&fs::read_to_string(&index)?)?;
|
||||
for (path, asset) in contents.objects {
|
||||
let hash = asset.hash.clone();
|
||||
let short = &hash[..2];
|
||||
|
||||
let file = if contents.map_to_resources {
|
||||
resources_dir.join(path)
|
||||
} else {
|
||||
objects.join(&short).join(&hash)
|
||||
};
|
||||
|
||||
fs::create_dir_all(file.parent().ok_or(anyhow!("Couldn't get asset parent"))?)?;
|
||||
|
||||
if !file.exists() {
|
||||
let url = format!("https://resources.download.minecraft.net/{}/{}", &short, hash);
|
||||
http::download_file_sha1_check(&url, &file, &asset.hash).await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok((
|
||||
manifest.asset_index.id.clone(),
|
||||
if contents.map_to_resources { resources_dir.clone() } else { assets }
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
struct VersionList {
|
||||
versions: Vec<MinecraftVersion>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct CachedVersions {
|
||||
last_updated: i64,
|
||||
versions: Vec<MinecraftVersion>,
|
||||
}
|
||||
|
||||
pub async fn get_versions(file: Option<&PathBuf>) -> crate::Result<Vec<MinecraftVersion>> {
|
||||
if let Some(file) = file {
|
||||
if file.exists() && file.is_file() {
|
||||
let cached = serde_json::from_str::<CachedVersions>(&fs::read_to_string(file)?)?;
|
||||
let head_request = http::create_client()?
|
||||
.head(MINECRAFT_VERSIONS_MANIFEST)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
let last_updated = head_request
|
||||
.headers()
|
||||
.get("Last-Modified")
|
||||
.ok_or(anyhow!("Last-Modified header not found"))?;
|
||||
|
||||
let last_updated = chrono::DateTime::parse_from_rfc2822(last_updated.to_str()?)?;
|
||||
|
||||
if cached.last_updated > last_updated.timestamp() {
|
||||
return Ok(cached.versions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let response = http::create_client()?
|
||||
.get(MINECRAFT_VERSIONS_MANIFEST)
|
||||
.send()
|
||||
.await?;
|
||||
let response = response.json::<VersionList>().await?;
|
||||
|
||||
if let Some(file) = file {
|
||||
fs::write(
|
||||
file,
|
||||
serde_json::to_string(&CachedVersions {
|
||||
last_updated: chrono::Utc::now().timestamp(),
|
||||
versions: response.versions.clone(),
|
||||
})?,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(response.versions)
|
||||
}
|
||||
|
||||
pub async fn retrieve_version_manifest(url: &str) -> crate::Result<MinecraftManifest> {
|
||||
let manifest = http::create_client()?
|
||||
.get(url)
|
||||
.send()
|
||||
.await?
|
||||
.json::<MinecraftManifest>().await;
|
||||
|
||||
if let Err(err) = manifest {
|
||||
println!("{:#?}", err);
|
||||
return Err(err.into());
|
||||
}
|
||||
Ok(manifest.unwrap())
|
||||
}
|
||||
|
|
@ -104,7 +104,7 @@ async fn download(
|
|||
let latest = response.as_array().unwrap().first().unwrap();
|
||||
let download_url = latest.get("download_url").unwrap().as_str().unwrap();
|
||||
|
||||
if let Err(err) = http::download_file(download_url, archive.as_path()).await {
|
||||
if let Err(err) = http::download_file(download_url, archive).await {
|
||||
return Err(ErrorKind::JavaError(JavaDownloadError::DownloadError(err.to_string())).into());
|
||||
};
|
||||
|
||||
|
|
@ -112,7 +112,7 @@ async fn download(
|
|||
}
|
||||
|
||||
fn extract(archive: &PathBuf, dest: &PathBuf) -> crate::Result<()> {
|
||||
if let Err(err) = file::extract_archive(archive.as_path(), dest.as_path()) {
|
||||
if let Err(err) = file::extract_archive(archive, dest) {
|
||||
let _ = fs::remove_file(dest.as_path());
|
||||
return Err(ErrorKind::JavaError(JavaDownloadError::ExtractError(err.to_string())).into());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,212 +2,313 @@ use core::fmt;
|
|||
use std::{collections::HashMap, marker::PhantomData};
|
||||
|
||||
use serde::{de::{self, SeqAccess, Visitor}, Deserialize, Deserializer, Serialize};
|
||||
use serde_json::Value;
|
||||
use crate::constants;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MinecraftVersion {
|
||||
pub id: String,
|
||||
pub url: String,
|
||||
#[serde(default)]
|
||||
pub release_type: ReleaseType,
|
||||
#[serde(default)]
|
||||
pub release_time: chrono::DateTime<chrono::Utc>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MinecraftManifest {
|
||||
pub asset_index: AssetIndex,
|
||||
pub downloads: Downloads,
|
||||
pub java_version: JavaVersion,
|
||||
pub libraries: Vec<Library>,
|
||||
pub logging: Logging,
|
||||
pub main_class: String,
|
||||
pub release_time: String,
|
||||
#[serde(rename = "id")]
|
||||
pub version: String,
|
||||
#[serde(rename = "type")]
|
||||
pub release_type: ReleaseType,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub minecraft_arguments
|
||||
pub asset_index: AssetIndex,
|
||||
pub downloads: Downloads,
|
||||
pub java_version: JavaVersion,
|
||||
pub libraries: Vec<Library>,
|
||||
pub main_class: String,
|
||||
pub release_time: String,
|
||||
|
||||
#[serde(rename = "id")]
|
||||
pub version: String,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub logging: Option<Logging>,
|
||||
|
||||
#[serde(rename = "type")]
|
||||
pub release_type: ReleaseType,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub minecraft_arguments: Option<String>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub arguments: Option<ModernArguments>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(untagged)]
|
||||
pub enum Arguments {
|
||||
// TODO: https://serde.rs/string-or-struct.html
|
||||
MinecraftArguments(String),
|
||||
Arguments {
|
||||
game: Vec<ModernArgumentsItem>,
|
||||
jvm: Vec<ModernArgumentsItem>,
|
||||
},
|
||||
pub struct ModernArguments {
|
||||
pub game: Vec<ModernArgumentsItem>,
|
||||
pub jvm: Vec<ModernArgumentsItem>,
|
||||
}
|
||||
|
||||
impl Default for Arguments {
|
||||
fn default() -> Self {
|
||||
Arguments::MinecraftArguments(String::new())
|
||||
}
|
||||
pub trait ModernArgumentsItemExt {
|
||||
fn build(&self) -> String;
|
||||
}
|
||||
|
||||
impl ModernArgumentsItemExt for [ModernArgumentsItem] {
|
||||
fn build(&self) -> String {
|
||||
// TODO: Implement the custom rules
|
||||
let mut builder = String::new();
|
||||
|
||||
for item in self {
|
||||
match item {
|
||||
ModernArgumentsItem::Simple(s) => {
|
||||
builder.push_str(s);
|
||||
builder.push(' ');
|
||||
},
|
||||
|
||||
ModernArgumentsItem::Rule(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
builder
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(untagged)]
|
||||
pub enum ModernArgumentsItem {
|
||||
Simple(String),
|
||||
Rule {
|
||||
rules: Vec<Rule>,
|
||||
value: ArgumentRuleValue,
|
||||
},
|
||||
Simple(String),
|
||||
Rule(ModernArgumentRuleItem),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(untagged)]
|
||||
pub enum ArgumentRuleValue {
|
||||
String(String),
|
||||
List(Vec<String>),
|
||||
pub struct ModernArgumentRuleItem {
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
pub rules: Vec<Rule>,
|
||||
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
#[serde(deserialize_with = "string_or_seq")]
|
||||
pub value: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ReleaseType {
|
||||
Snapshot,
|
||||
Release,
|
||||
OldBeta,
|
||||
OldAlpha,
|
||||
Snapshot,
|
||||
Release,
|
||||
OldBeta,
|
||||
OldAlpha,
|
||||
}
|
||||
|
||||
impl Default for ReleaseType {
|
||||
fn default() -> Self {
|
||||
ReleaseType::Release
|
||||
}
|
||||
fn default() -> Self { ReleaseType::Release }
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AssetIndex {
|
||||
pub id: String,
|
||||
pub sha1: String,
|
||||
pub url: String,
|
||||
pub id: String,
|
||||
pub sha1: String,
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Downloads {
|
||||
pub client: Client,
|
||||
pub client: Client,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Client {
|
||||
pub sha1: String,
|
||||
pub url: String,
|
||||
pub sha1: String,
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct JavaVersion {
|
||||
pub major_version: u8,
|
||||
pub major_version: u8,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Library {
|
||||
pub downloads: Downloads2,
|
||||
pub name: String,
|
||||
#[serde(default)]
|
||||
pub rules: Vec<Rule>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub natives: Option<Natives>,
|
||||
pub downloads: LibraryDownload,
|
||||
pub name: String,
|
||||
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
||||
pub rules: Vec<Rule>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub natives: Option<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Downloads2 {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub artifact: Option<Artifact>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub classifiers: Option<Classifiers>,
|
||||
pub struct LibraryDownload {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub artifact: Option<Artifact>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub classifiers: Option<HashMap<String, Artifact>>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Artifact {
|
||||
pub path: String,
|
||||
pub sha1: String,
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Classifiers {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(rename = "natives-osx")]
|
||||
pub natives_osx: Option<Native>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(rename = "natives-linux")]
|
||||
pub natives_linux: Option<Native>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(rename = "natives-windows")]
|
||||
pub natives_windows: Option<Native>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Native {
|
||||
pub path: String,
|
||||
pub sha1: String,
|
||||
pub url: String,
|
||||
pub path: String,
|
||||
pub sha1: String,
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Rule {
|
||||
pub action: RuleAction,
|
||||
pub action: RuleAction,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub os: Option<Os>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub os: Option<Os>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub features: Option<HashMap<String, bool>>,
|
||||
}
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub features: Option<Value>,
|
||||
pub trait RuleExt {
|
||||
fn check(&self) -> bool;
|
||||
}
|
||||
|
||||
pub trait RuleListExt {
|
||||
fn check(&self) -> bool;
|
||||
}
|
||||
|
||||
impl RuleListExt for Vec<Rule> {
|
||||
fn check(&self) -> bool {
|
||||
for rule in self {
|
||||
if !rule.check() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl RuleExt for Rule {
|
||||
fn check(&self) -> bool {
|
||||
if let Some(os) = &self.os {
|
||||
match &self.action {
|
||||
RuleAction::Allow => {
|
||||
// os name check
|
||||
if let Some(name) = &os.name {
|
||||
if name != constants::TARGET_OS {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: os version check
|
||||
// os version check
|
||||
|
||||
// TODO: os arch check
|
||||
// os arch check
|
||||
},
|
||||
|
||||
RuleAction::Disallow => {
|
||||
// os name check
|
||||
if let Some(name) = &os.name {
|
||||
if name == constants::TARGET_OS {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(untagged)]
|
||||
pub enum RuleAction {
|
||||
Allow,
|
||||
Disallow,
|
||||
#[serde(rename = "allow")]
|
||||
Allow,
|
||||
#[serde(rename = "disallow")]
|
||||
Disallow,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Os {
|
||||
pub name: Option<String>,
|
||||
pub arch: Option<String>,
|
||||
pub version: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Natives {
|
||||
pub windows: Option<String>,
|
||||
pub linux: Option<String>,
|
||||
pub osx: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub name: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub arch: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub version: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Logging {
|
||||
pub client: Client2,
|
||||
pub client: Client2,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Client2 {
|
||||
pub argument: String,
|
||||
pub file: File,
|
||||
#[serde(rename = "type")]
|
||||
pub type_field: String,
|
||||
pub argument: String,
|
||||
pub file: File,
|
||||
#[serde(rename = "type")]
|
||||
pub type_field: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct File {
|
||||
pub id: String,
|
||||
pub sha1: String,
|
||||
pub url: String,
|
||||
pub id: String,
|
||||
pub sha1: String,
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct AssetIndexFile {
|
||||
#[serde(default = "default_false")]
|
||||
pub map_to_resources: bool,
|
||||
pub objects: HashMap<String, AssetObject>,
|
||||
}
|
||||
|
||||
fn default_false() -> bool { false }
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct AssetObject {
|
||||
pub hash: String,
|
||||
pub size: u64,
|
||||
}
|
||||
|
||||
fn string_or_seq<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct StringOrVec(PhantomData<Vec<String>>);
|
||||
|
||||
impl<'de> Visitor<'de> for StringOrVec {
|
||||
type Value = Vec<String>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("string or list of strings")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
Ok(vec![value.into()])
|
||||
}
|
||||
|
||||
fn visit_seq<S>(self, seq: S) -> Result<Self::Value, S::Error>
|
||||
where
|
||||
S: SeqAccess<'de>,
|
||||
{
|
||||
Deserialize::deserialize(de::value::SeqAccessDeserializer::new(seq))
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_any(StringOrVec(PhantomData))
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
pub mod client;
|
||||
pub mod java;
|
||||
pub mod minecraft;
|
||||
pub mod vanilla;
|
||||
pub mod clients;
|
||||
pub mod client_manager;
|
||||
|
|
|
|||
|
|
@ -1,144 +0,0 @@
|
|||
use super::{
|
||||
client::{ClientTrait, Instance, Manifest, Version},
|
||||
minecraft::MinecraftManifest,
|
||||
};
|
||||
use crate::{
|
||||
constants::MINECRAFT_VERSIONS_MANIFEST,
|
||||
create_game_client, impl_game_client,
|
||||
utils::{dirs, file, http},
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use async_trait::async_trait;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{fs, path::PathBuf};
|
||||
|
||||
create_game_client! {
|
||||
VanillaClientProps {}
|
||||
|
||||
VanillaClient {}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<'a> ClientTrait<'a> for VanillaClient<'a> {
|
||||
impl_game_client!();
|
||||
|
||||
fn new(instance: &'a Instance, manifest: &'a Manifest) -> Self {
|
||||
VanillaClient { instance, manifest }
|
||||
}
|
||||
|
||||
async fn launch(&self) -> crate::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn setup(&self) -> crate::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn install_game(&self) -> crate::Result<PathBuf> {
|
||||
let manifest = &self.manifest.manifest;
|
||||
let file = dirs::clients_dir()?.join(format!("{}.jar", manifest.version));
|
||||
|
||||
if !file.exists() {
|
||||
fs::create_dir_all(&file)?;
|
||||
let artifact = &manifest.downloads.client;
|
||||
|
||||
http::download_file(&artifact.url, &file).await?;
|
||||
let file_hash = file::file_sha1(&file)?;
|
||||
|
||||
println!("Downloaded: '{}' | '{}'", file_hash, artifact.sha1);
|
||||
if file_hash != artifact.sha1 {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
"Hashes do not match",
|
||||
)
|
||||
.into());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(file)
|
||||
}
|
||||
|
||||
async fn install_libraries(&self) -> crate::Result<String> {
|
||||
Ok("".to_string())
|
||||
}
|
||||
|
||||
async fn install_natives(&self) -> crate::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn install_assets(&self) -> crate::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
struct VersionList {
|
||||
versions: Vec<Version>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct CachedVersions {
|
||||
last_updated: i64,
|
||||
versions: Vec<Version>,
|
||||
}
|
||||
|
||||
pub async fn get_versions(file: Option<&PathBuf>) -> crate::Result<Vec<Version>> {
|
||||
if let Some(file) = file {
|
||||
if file.exists() && file.is_file() {
|
||||
if let Some(versions) = get_cached_versions(file).await? {
|
||||
return Ok(versions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let response = http::create_client()?
|
||||
.get(MINECRAFT_VERSIONS_MANIFEST)
|
||||
.send()
|
||||
.await?;
|
||||
let response = response.json::<VersionList>().await?;
|
||||
|
||||
if let Some(file) = file {
|
||||
fs::write(
|
||||
file,
|
||||
serde_json::to_string(&CachedVersions {
|
||||
last_updated: chrono::Utc::now().timestamp(),
|
||||
versions: response.versions.clone(),
|
||||
})?,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(response.versions)
|
||||
}
|
||||
|
||||
async fn get_cached_versions(file: &PathBuf) -> crate::Result<Option<Vec<Version>>> {
|
||||
let cached = serde_json::from_str::<CachedVersions>(&fs::read_to_string(file)?)?;
|
||||
let head_request = http::create_client()?
|
||||
.head(MINECRAFT_VERSIONS_MANIFEST)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
let last_updated = head_request
|
||||
.headers()
|
||||
.get("Last-Modified")
|
||||
.ok_or(anyhow!("Last-Modified header not found"))?;
|
||||
let last_updated = last_updated
|
||||
.to_str()
|
||||
.map_err(|_| anyhow!("Failed to convert Last-Modified header to string"))?;
|
||||
let last_updated = chrono::DateTime::parse_from_rfc2822(last_updated)?;
|
||||
|
||||
if cached.last_updated < last_updated.timestamp() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(cached.versions))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn retrieve_version_manifest(url: &str) -> crate::Result<MinecraftManifest> {
|
||||
let manifest = http::create_client()?
|
||||
.get(url)
|
||||
.send()
|
||||
.await?
|
||||
.json::<MinecraftManifest>()
|
||||
.await?;
|
||||
Ok(manifest)
|
||||
}
|
||||
|
|
@ -7,6 +7,24 @@ pub mod error;
|
|||
pub mod game;
|
||||
pub mod logger;
|
||||
pub mod utils;
|
||||
pub mod settings;
|
||||
|
||||
pub use error::*;
|
||||
pub use logger::start_logger;
|
||||
|
||||
pub struct AppState {
|
||||
pub settings: settings::SettingsManager,
|
||||
pub clients: game::client_manager::ClientManager,
|
||||
}
|
||||
|
||||
unsafe impl Send for AppState {}
|
||||
unsafe impl Sync for AppState {}
|
||||
|
||||
impl AppState {
|
||||
pub fn new() -> crate::Result<Self> {
|
||||
Ok(Self {
|
||||
settings: settings::SettingsManager::new()?,
|
||||
clients: game::client_manager::ClientManager::new()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
61
onelauncher/src/settings.rs
Normal file
61
onelauncher/src/settings.rs
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
use std::fs;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::utils::dirs;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct Setings {
|
||||
pub discord_rpc: bool,
|
||||
}
|
||||
|
||||
impl Default for Setings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
discord_rpc: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SettingsManager {
|
||||
pub settings: Setings,
|
||||
}
|
||||
|
||||
impl SettingsManager {
|
||||
pub fn new() -> crate::Result<Self> {
|
||||
let path = dirs::app_config_dir()?.join("settings.json");
|
||||
|
||||
if path.exists() {
|
||||
let file = fs::read_to_string(path)?;
|
||||
let settings: Setings = serde_json::from_str(&file)?;
|
||||
|
||||
return Ok(Self {
|
||||
settings
|
||||
});
|
||||
}
|
||||
|
||||
let this = Self {
|
||||
settings: Setings::default()
|
||||
};
|
||||
|
||||
this.save()?;
|
||||
Ok(this)
|
||||
}
|
||||
|
||||
pub fn get_settings(&self) -> &Setings {
|
||||
&self.settings
|
||||
}
|
||||
|
||||
pub fn get_settings_mut(&mut self) -> &mut Setings {
|
||||
&mut self.settings
|
||||
}
|
||||
|
||||
pub fn save(&self) -> crate::Result<()> {
|
||||
let path = dirs::app_config_dir()?.join("settings.json");
|
||||
fs::create_dir_all(path.parent().unwrap())?;
|
||||
fs::write(path, serde_json::to_string(&self.settings)?)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -14,20 +14,28 @@ pub fn app_config_dir() -> crate::Result<PathBuf> {
|
|||
.ok_or(DirectoryError::ConfigDir.into())
|
||||
}
|
||||
|
||||
pub fn assets_dir() -> crate::Result<PathBuf> {
|
||||
Ok(app_config_dir()?.join("assets"))
|
||||
}
|
||||
|
||||
pub fn libraries_dir() -> crate::Result<PathBuf> {
|
||||
Ok(app_config_dir()?.join("libraries"))
|
||||
}
|
||||
|
||||
pub fn natives_dir() -> crate::Result<PathBuf> {
|
||||
Ok(app_config_dir()?.join("natives"))
|
||||
}
|
||||
|
||||
pub fn java_dir() -> crate::Result<PathBuf> {
|
||||
Ok(app_config_dir()?.join("java"))
|
||||
}
|
||||
|
||||
pub fn instances_dir() -> crate::Result<PathBuf> {
|
||||
Ok(app_config_dir()?.join("instances"))
|
||||
pub fn clusters_dir() -> crate::Result<PathBuf> {
|
||||
Ok(app_config_dir()?.join("clusters"))
|
||||
}
|
||||
|
||||
pub fn manifests_dir() -> crate::Result<PathBuf> {
|
||||
Ok(app_config_dir()?.join("manifests"))
|
||||
pub fn cluster_dir(uuid: String) -> crate::Result<PathBuf> {
|
||||
Ok(clusters_dir()?.join(uuid))
|
||||
}
|
||||
|
||||
pub fn clients_dir() -> crate::Result<PathBuf> {
|
||||
|
|
|
|||
|
|
@ -3,23 +3,22 @@ use sha1::{Digest, Sha1};
|
|||
use std::{
|
||||
fs::File,
|
||||
io::BufReader,
|
||||
path::{Path, PathBuf},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use crate::ErrorKind;
|
||||
|
||||
pub fn file_sha1(file: &PathBuf) -> crate::Result<String> {
|
||||
let file = File::open(file)?;
|
||||
let reader = BufReader::new(file);
|
||||
|
||||
let mut file = File::open(file)?;
|
||||
let mut hasher = Sha1::new();
|
||||
hasher.update(&reader.buffer());
|
||||
let hash = hasher.finalize();
|
||||
let mut reader = BufReader::new(&mut file);
|
||||
std::io::copy(&mut reader, &mut hasher)?;
|
||||
let hash = hasher.finalize();
|
||||
|
||||
Ok(format!("{:x}", hash))
|
||||
}
|
||||
|
||||
pub fn extract_archive(archive: &Path, dest: &Path) -> crate::Result<()> {
|
||||
pub fn extract_archive(archive: &PathBuf, dest: &PathBuf) -> crate::Result<()> {
|
||||
let ext = match archive.extension() {
|
||||
Some(ext) => ext.to_str().unwrap(),
|
||||
None => return Err(ErrorKind::AnyhowError(anyhow!("unsupported operating system")).into()),
|
||||
|
|
@ -32,7 +31,7 @@ pub fn extract_archive(archive: &Path, dest: &Path) -> crate::Result<()> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn extract_zip(archive: &Path, dest: &Path) -> crate::Result<()> {
|
||||
pub fn extract_zip(archive: &PathBuf, dest: &PathBuf) -> crate::Result<()> {
|
||||
let file = File::open(archive).map_err(|err| ErrorKind::IOError(err))?;
|
||||
let archive = zip::ZipArchive::new(file).map_err(|err| ErrorKind::ZipError(err));
|
||||
archive?
|
||||
|
|
@ -41,7 +40,7 @@ pub fn extract_zip(archive: &Path, dest: &Path) -> crate::Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn extract_tar_gz(archive: &Path, dest: &Path) -> crate::Result<()> {
|
||||
pub fn extract_tar_gz(archive: &PathBuf, dest: &PathBuf) -> crate::Result<()> {
|
||||
let file = File::open(archive).map_err(|err| ErrorKind::IOError(err))?;
|
||||
let tar_gz = flate2::read::GzDecoder::new(file);
|
||||
let mut archive = tar::Archive::new(tar_gz);
|
||||
|
|
|
|||
|
|
@ -1,34 +1,39 @@
|
|||
use std::{
|
||||
fs::File,
|
||||
io::{self, Cursor},
|
||||
path::Path,
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use reqwest::{Client, ClientBuilder};
|
||||
|
||||
use crate::{constants::USER_AGENT, ErrorKind};
|
||||
|
||||
use super::file::file_sha1;
|
||||
|
||||
pub fn create_client() -> crate::Result<Client> {
|
||||
Ok(ClientBuilder::new()
|
||||
.user_agent(USER_AGENT)
|
||||
.build()
|
||||
.map_err(|err| ErrorKind::HTTPError(err))?)
|
||||
Ok(ClientBuilder::new().user_agent(USER_AGENT).build()?)
|
||||
}
|
||||
|
||||
// can we not make mapping the errors automatic, isnt that the whole point
|
||||
pub async fn download_file(url: &str, path: &Path) -> crate::Result<()> {
|
||||
let response = create_client()?
|
||||
.get(url)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|err| ErrorKind::HTTPError(err))?;
|
||||
pub async fn download_file_sha1_check(url: &str, path: &PathBuf, sha1: &str) -> crate::Result<()> {
|
||||
download_file(url, path).await?;
|
||||
|
||||
let file_hash = file_sha1(path)?;
|
||||
if file_hash != sha1 {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
"Hashes do not match",
|
||||
).into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn download_file(url: &str, path: &PathBuf) -> crate::Result<()> {
|
||||
let response = create_client()?.get(url).send().await?;
|
||||
let mut file = File::create(path).map_err(|err| ErrorKind::IOError(err))?;
|
||||
let mut content = Cursor::new(
|
||||
response
|
||||
.bytes()
|
||||
.await
|
||||
.map_err(|err| ErrorKind::HTTPError(err))?,
|
||||
);
|
||||
let mut content = Cursor::new(response.bytes().await?);
|
||||
|
||||
io::copy(&mut content, &mut file).map_err(|err| ErrorKind::IOError(err))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
312
pnpm-lock.yaml
generated
312
pnpm-lock.yaml
generated
|
|
@ -8,9 +8,6 @@ dependencies:
|
|||
'@fontsource/poppins':
|
||||
specifier: ^5.0.12
|
||||
version: 5.0.12
|
||||
'@sentry/vite-plugin':
|
||||
specifier: ^2.16.0
|
||||
version: 2.16.0
|
||||
'@solidjs/router':
|
||||
specifier: ^0.13.1
|
||||
version: 0.13.1(solid-js@1.8.16)
|
||||
|
|
@ -104,6 +101,7 @@ packages:
|
|||
dependencies:
|
||||
'@jridgewell/gen-mapping': 0.3.5
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
dev: true
|
||||
|
||||
/@antfu/eslint-define-config@1.23.0-2:
|
||||
resolution: {integrity: sha512-LvxY21+ZhpuBf/aHeBUtGQhSEfad4PkNKXKvDOSvukaM3XVTfBhwmHX2EKwAsdq5DlfjbT3qqYyMiueBIO5iDQ==}
|
||||
|
|
@ -122,6 +120,7 @@ packages:
|
|||
dependencies:
|
||||
'@babel/highlight': 7.23.4
|
||||
chalk: 2.4.2
|
||||
dev: true
|
||||
|
||||
/@babel/code-frame@7.24.2:
|
||||
resolution: {integrity: sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==}
|
||||
|
|
@ -134,6 +133,7 @@ packages:
|
|||
/@babel/compat-data@7.23.5:
|
||||
resolution: {integrity: sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dev: true
|
||||
|
||||
/@babel/core@7.24.0:
|
||||
resolution: {integrity: sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR+K9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw==}
|
||||
|
|
@ -156,6 +156,7 @@ packages:
|
|||
semver: 6.3.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@babel/generator@7.23.6:
|
||||
resolution: {integrity: sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==}
|
||||
|
|
@ -165,6 +166,7 @@ packages:
|
|||
'@jridgewell/gen-mapping': 0.3.5
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
jsesc: 2.5.2
|
||||
dev: true
|
||||
|
||||
/@babel/helper-compilation-targets@7.23.6:
|
||||
resolution: {integrity: sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==}
|
||||
|
|
@ -175,10 +177,12 @@ packages:
|
|||
browserslist: 4.23.0
|
||||
lru-cache: 5.1.1
|
||||
semver: 6.3.1
|
||||
dev: true
|
||||
|
||||
/@babel/helper-environment-visitor@7.22.20:
|
||||
resolution: {integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dev: true
|
||||
|
||||
/@babel/helper-function-name@7.23.0:
|
||||
resolution: {integrity: sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==}
|
||||
|
|
@ -186,12 +190,14 @@ packages:
|
|||
dependencies:
|
||||
'@babel/template': 7.24.0
|
||||
'@babel/types': 7.24.0
|
||||
dev: true
|
||||
|
||||
/@babel/helper-hoist-variables@7.22.5:
|
||||
resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@babel/types': 7.24.0
|
||||
dev: true
|
||||
|
||||
/@babel/helper-module-imports@7.18.6:
|
||||
resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==}
|
||||
|
|
@ -205,6 +211,7 @@ packages:
|
|||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@babel/types': 7.24.0
|
||||
dev: true
|
||||
|
||||
/@babel/helper-module-transforms@7.23.3(@babel/core@7.24.0):
|
||||
resolution: {integrity: sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==}
|
||||
|
|
@ -218,6 +225,7 @@ packages:
|
|||
'@babel/helper-simple-access': 7.22.5
|
||||
'@babel/helper-split-export-declaration': 7.22.6
|
||||
'@babel/helper-validator-identifier': 7.22.20
|
||||
dev: true
|
||||
|
||||
/@babel/helper-plugin-utils@7.24.0:
|
||||
resolution: {integrity: sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==}
|
||||
|
|
@ -229,24 +237,29 @@ packages:
|
|||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@babel/types': 7.24.0
|
||||
dev: true
|
||||
|
||||
/@babel/helper-split-export-declaration@7.22.6:
|
||||
resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@babel/types': 7.24.0
|
||||
dev: true
|
||||
|
||||
/@babel/helper-string-parser@7.23.4:
|
||||
resolution: {integrity: sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dev: true
|
||||
|
||||
/@babel/helper-validator-identifier@7.22.20:
|
||||
resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dev: true
|
||||
|
||||
/@babel/helper-validator-option@7.23.5:
|
||||
resolution: {integrity: sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dev: true
|
||||
|
||||
/@babel/helpers@7.24.0:
|
||||
resolution: {integrity: sha512-ulDZdc0Aj5uLc5nETsa7EPx2L7rM0YJM8r7ck7U73AXi7qOV44IHHRAYZHY6iU1rr3C5N4NtTmMRUJP6kwCWeA==}
|
||||
|
|
@ -257,6 +270,7 @@ packages:
|
|||
'@babel/types': 7.24.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@babel/highlight@7.23.4:
|
||||
resolution: {integrity: sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==}
|
||||
|
|
@ -265,6 +279,7 @@ packages:
|
|||
'@babel/helper-validator-identifier': 7.22.20
|
||||
chalk: 2.4.2
|
||||
js-tokens: 4.0.0
|
||||
dev: true
|
||||
|
||||
/@babel/highlight@7.24.2:
|
||||
resolution: {integrity: sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==}
|
||||
|
|
@ -282,6 +297,7 @@ packages:
|
|||
hasBin: true
|
||||
dependencies:
|
||||
'@babel/types': 7.24.0
|
||||
dev: true
|
||||
|
||||
/@babel/parser@7.24.1:
|
||||
resolution: {integrity: sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg==}
|
||||
|
|
@ -315,6 +331,7 @@ packages:
|
|||
'@babel/code-frame': 7.23.5
|
||||
'@babel/parser': 7.24.0
|
||||
'@babel/types': 7.24.0
|
||||
dev: true
|
||||
|
||||
/@babel/traverse@7.24.0:
|
||||
resolution: {integrity: sha512-HfuJlI8qq3dEDmNU5ChzzpZRWq+oxCZQyMzIMEqLho+AQnhMnKQUzH6ydo3RBl/YjPCuk68Y6s0Gx0AeyULiWw==}
|
||||
|
|
@ -332,6 +349,7 @@ packages:
|
|||
globals: 11.12.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@babel/types@7.24.0:
|
||||
resolution: {integrity: sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==}
|
||||
|
|
@ -340,6 +358,7 @@ packages:
|
|||
'@babel/helper-string-parser': 7.23.4
|
||||
'@babel/helper-validator-identifier': 7.22.20
|
||||
to-fast-properties: 2.0.0
|
||||
dev: true
|
||||
|
||||
/@es-joy/jsdoccomment@0.42.0:
|
||||
resolution: {integrity: sha512-R1w57YlVA6+YE01wch3GPYn6bCsrOV3YW/5oGGE2tmX6JcL9Nr+b5IikrjMPF+v9CV3ay+obImEdsDhovhJrzw==}
|
||||
|
|
@ -741,23 +760,28 @@ packages:
|
|||
'@jridgewell/set-array': 1.2.1
|
||||
'@jridgewell/sourcemap-codec': 1.4.15
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
dev: true
|
||||
|
||||
/@jridgewell/resolve-uri@3.1.2:
|
||||
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
dev: true
|
||||
|
||||
/@jridgewell/set-array@1.2.1:
|
||||
resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
dev: true
|
||||
|
||||
/@jridgewell/sourcemap-codec@1.4.15:
|
||||
resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
|
||||
dev: true
|
||||
|
||||
/@jridgewell/trace-mapping@0.3.25:
|
||||
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
|
||||
dependencies:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.4.15
|
||||
dev: true
|
||||
|
||||
/@nodelib/fs.scandir@2.1.5:
|
||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||
|
|
@ -891,125 +915,6 @@ packages:
|
|||
dev: true
|
||||
optional: true
|
||||
|
||||
/@sentry/babel-plugin-component-annotate@2.16.0:
|
||||
resolution: {integrity: sha512-+uy1qPkA5MSNgJ0L9ur/vNTydfdHwHnBX2RQ+0thsvkqf90fU788YjkkXwUiBBNuqNyI69JiOW6frixAWy7oUg==}
|
||||
engines: {node: '>= 14'}
|
||||
dev: false
|
||||
|
||||
/@sentry/bundler-plugin-core@2.16.0:
|
||||
resolution: {integrity: sha512-dhgIZsIR3L9KnE2OO5JJm6hPtStAjEPYKQsZzxRr69uVhd9xAvfXeXr0afKVNVEcIDksas6yMgHqwQ2wOXFIAg==}
|
||||
engines: {node: '>= 14'}
|
||||
dependencies:
|
||||
'@babel/core': 7.24.0
|
||||
'@sentry/babel-plugin-component-annotate': 2.16.0
|
||||
'@sentry/cli': 2.30.2
|
||||
dotenv: 16.4.5
|
||||
find-up: 5.0.0
|
||||
glob: 9.3.5
|
||||
magic-string: 0.27.0
|
||||
unplugin: 1.0.1
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/@sentry/cli-darwin@2.30.2:
|
||||
resolution: {integrity: sha512-lZkKXMt0HUAwLQuPpi/DM3CsdCCp+6B2cdur+8fAq7uARXTOsTKVDxv9pkuJHCgHUnguh8ittP5GMr0baTxmMg==}
|
||||
engines: {node: '>=10'}
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@sentry/cli-linux-arm64@2.30.2:
|
||||
resolution: {integrity: sha512-IWassuXggNhHOPCNrORNmd5SrAx5rU4XDlgOWBJr/ez7DvlPrr9EhV1xsdht6K4mPXhCGJq3rtRdCoWGJQW6Uw==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [arm64]
|
||||
os: [linux, freebsd]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@sentry/cli-linux-arm@2.30.2:
|
||||
resolution: {integrity: sha512-H7hqiLpEL7w/EHdhuUGatwg9O080mdujq4/zS96buKIHXxZE6KqMXGtMVIAvTl1+z6BlBEnfvZGI19MPw3t/7w==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [arm]
|
||||
os: [linux, freebsd]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@sentry/cli-linux-i686@2.30.2:
|
||||
resolution: {integrity: sha512-gZIq131M4TJTG1lX9uvpoaGWaEXCEfdDXrXu/z/YZmAKBcThpMYChodXmm8FB6X4xb0TPXzIFqdzlLdglFK46g==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [x86, ia32]
|
||||
os: [linux, freebsd]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@sentry/cli-linux-x64@2.30.2:
|
||||
resolution: {integrity: sha512-NmTAIl7aW9OHxwB4149sBfvCbTyK9T/CvBX38keaD2yIThet9gZ4koP49hBDxYF99aQX3E+LIAqWwnkV9W72Sw==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [x64]
|
||||
os: [linux, freebsd]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@sentry/cli-win32-i686@2.30.2:
|
||||
resolution: {integrity: sha512-SBR/Q3T6o+7uHwHNdjcG9GA3R++9w8oi778b95GuOC3dh0WOU6hXaKwQWe95ZcuSd2rKpouH7dhMjqqNM4HxOA==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [x86, ia32]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@sentry/cli-win32-x64@2.30.2:
|
||||
resolution: {integrity: sha512-gF9wSZxzXFgakkC+uKVLAAYlbYj13e1gTsNm3gm+ODfpV+rbHwvbKoLfNsbVCFVCEZxIV2rXEP5WmTr0kiMvWQ==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@sentry/cli@2.30.2:
|
||||
resolution: {integrity: sha512-jQ/RBJ3bZ4PFbfOsGq8EykygHHmXXPw+i6jqsnQfAPIeZoX+DsqpAZbYubQEZKekmQ8EVGFxGHzUVkd6hLVMbA==}
|
||||
engines: {node: '>= 10'}
|
||||
hasBin: true
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
https-proxy-agent: 5.0.1
|
||||
node-fetch: 2.7.0
|
||||
progress: 2.0.3
|
||||
proxy-from-env: 1.1.0
|
||||
which: 2.0.2
|
||||
optionalDependencies:
|
||||
'@sentry/cli-darwin': 2.30.2
|
||||
'@sentry/cli-linux-arm': 2.30.2
|
||||
'@sentry/cli-linux-arm64': 2.30.2
|
||||
'@sentry/cli-linux-i686': 2.30.2
|
||||
'@sentry/cli-linux-x64': 2.30.2
|
||||
'@sentry/cli-win32-i686': 2.30.2
|
||||
'@sentry/cli-win32-x64': 2.30.2
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/@sentry/vite-plugin@2.16.0:
|
||||
resolution: {integrity: sha512-/7rFTpasql9fJ1KbruF0yDESKV/ojvOP9pL/qqwUffvA9iy9Bvw3kYzzyzd1YsiCiVBgftoCsFTKPcvL3A4rwQ==}
|
||||
engines: {node: '>= 14'}
|
||||
dependencies:
|
||||
'@sentry/bundler-plugin-core': 2.16.0
|
||||
unplugin: 1.0.1
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/@solid-primitives/refs@1.0.8(solid-js@1.8.16):
|
||||
resolution: {integrity: sha512-+jIsWG8/nYvhaCoG2Vg6CJOLgTmPKFbaCrNQKWfChalgUf9WrVxWw0CdJb3yX15n5lUcQ0jBo6qYtuVVmBLpBw==}
|
||||
peerDependencies:
|
||||
|
|
@ -1630,15 +1535,7 @@ packages:
|
|||
resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
hasBin: true
|
||||
|
||||
/agent-base@6.0.2:
|
||||
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
|
||||
engines: {node: '>= 6.0.0'}
|
||||
dependencies:
|
||||
debug: 4.3.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
dev: true
|
||||
|
||||
/ajv@6.12.6:
|
||||
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
|
||||
|
|
@ -1664,6 +1561,7 @@ packages:
|
|||
engines: {node: '>=4'}
|
||||
dependencies:
|
||||
color-convert: 1.9.3
|
||||
dev: true
|
||||
|
||||
/ansi-styles@4.3.0:
|
||||
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
|
||||
|
|
@ -1687,6 +1585,7 @@ packages:
|
|||
dependencies:
|
||||
normalize-path: 3.0.0
|
||||
picomatch: 2.3.1
|
||||
dev: true
|
||||
|
||||
/are-docs-informative@0.0.2:
|
||||
resolution: {integrity: sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==}
|
||||
|
|
@ -1828,10 +1727,12 @@ packages:
|
|||
|
||||
/balanced-match@1.0.2:
|
||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||
dev: true
|
||||
|
||||
/binary-extensions@2.3.0:
|
||||
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/boolbase@1.0.0:
|
||||
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
|
||||
|
|
@ -1848,12 +1749,14 @@ packages:
|
|||
resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
dev: true
|
||||
|
||||
/braces@3.0.2:
|
||||
resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
fill-range: 7.0.1
|
||||
dev: true
|
||||
|
||||
/browserslist@4.23.0:
|
||||
resolution: {integrity: sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==}
|
||||
|
|
@ -1864,6 +1767,7 @@ packages:
|
|||
electron-to-chromium: 1.4.715
|
||||
node-releases: 2.0.14
|
||||
update-browserslist-db: 1.0.13(browserslist@4.23.0)
|
||||
dev: true
|
||||
|
||||
/builtin-modules@3.3.0:
|
||||
resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==}
|
||||
|
|
@ -1899,6 +1803,7 @@ packages:
|
|||
|
||||
/caniuse-lite@1.0.30001600:
|
||||
resolution: {integrity: sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ==}
|
||||
dev: true
|
||||
|
||||
/chalk@2.4.2:
|
||||
resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
|
||||
|
|
@ -1907,6 +1812,7 @@ packages:
|
|||
ansi-styles: 3.2.1
|
||||
escape-string-regexp: 1.0.5
|
||||
supports-color: 5.5.0
|
||||
dev: true
|
||||
|
||||
/chalk@4.1.2:
|
||||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
||||
|
|
@ -1941,6 +1847,7 @@ packages:
|
|||
readdirp: 3.6.0
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
dev: true
|
||||
|
||||
/ci-info@4.0.0:
|
||||
resolution: {integrity: sha512-TdHqgGf9odd8SXNuxtUBVx8Nv+qZOejE6qyqiy5NtbYYQOeFa6zmHkxlPzmaLxWWHsU6nJmB7AETdVPi+2NBUg==}
|
||||
|
|
@ -1958,6 +1865,7 @@ packages:
|
|||
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
|
||||
dependencies:
|
||||
color-name: 1.1.3
|
||||
dev: true
|
||||
|
||||
/color-convert@2.0.1:
|
||||
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
||||
|
|
@ -1968,6 +1876,7 @@ packages:
|
|||
|
||||
/color-name@1.1.3:
|
||||
resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==}
|
||||
dev: true
|
||||
|
||||
/color-name@1.1.4:
|
||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||
|
|
@ -1989,6 +1898,7 @@ packages:
|
|||
|
||||
/convert-source-map@2.0.0:
|
||||
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
||||
dev: true
|
||||
|
||||
/core-js-compat@3.36.1:
|
||||
resolution: {integrity: sha512-Dk997v9ZCt3X/npqzyGdTlq6t7lDBhZwGvV94PKzDArjp7BTRm7WlDAXYd/OWdeFHO8OChQYRJNJvUCqCbrtKA==}
|
||||
|
|
@ -2066,6 +1976,7 @@ packages:
|
|||
optional: true
|
||||
dependencies:
|
||||
ms: 2.1.2
|
||||
dev: true
|
||||
|
||||
/deep-is@0.1.4:
|
||||
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
|
||||
|
|
@ -2116,17 +2027,13 @@ packages:
|
|||
esutils: 2.0.3
|
||||
dev: true
|
||||
|
||||
/dotenv@16.4.5:
|
||||
resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==}
|
||||
engines: {node: '>=12'}
|
||||
dev: false
|
||||
|
||||
/eastasianwidth@0.2.0:
|
||||
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
||||
dev: true
|
||||
|
||||
/electron-to-chromium@1.4.715:
|
||||
resolution: {integrity: sha512-XzWNH4ZSa9BwVUQSDorPWAUQ5WGuYz7zJUNpNif40zFCiCl20t8zgylmreNmn26h5kiyw2lg7RfTmeMBsDklqg==}
|
||||
dev: true
|
||||
|
||||
/emoji-regex@8.0.0:
|
||||
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
||||
|
|
@ -2296,10 +2203,12 @@ packages:
|
|||
/escalade@3.1.2:
|
||||
resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==}
|
||||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/escape-string-regexp@1.0.5:
|
||||
resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
|
||||
engines: {node: '>=0.8.0'}
|
||||
dev: true
|
||||
|
||||
/escape-string-regexp@4.0.0:
|
||||
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
|
||||
|
|
@ -2853,6 +2762,7 @@ packages:
|
|||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
to-regex-range: 5.0.1
|
||||
dev: true
|
||||
|
||||
/find-up-simple@1.0.0:
|
||||
resolution: {integrity: sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==}
|
||||
|
|
@ -2873,6 +2783,7 @@ packages:
|
|||
dependencies:
|
||||
locate-path: 6.0.0
|
||||
path-exists: 4.0.0
|
||||
dev: true
|
||||
|
||||
/find-up@7.0.0:
|
||||
resolution: {integrity: sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==}
|
||||
|
|
@ -2916,12 +2827,14 @@ packages:
|
|||
|
||||
/fs.realpath@1.0.0:
|
||||
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
|
||||
dev: true
|
||||
|
||||
/fsevents@2.3.3:
|
||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/function-bind@1.1.2:
|
||||
|
|
@ -2945,6 +2858,7 @@ packages:
|
|||
/gensync@1.0.0-beta.2:
|
||||
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dev: true
|
||||
|
||||
/get-intrinsic@1.2.4:
|
||||
resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==}
|
||||
|
|
@ -2982,6 +2896,7 @@ packages:
|
|||
engines: {node: '>= 6'}
|
||||
dependencies:
|
||||
is-glob: 4.0.3
|
||||
dev: true
|
||||
|
||||
/glob-parent@6.0.2:
|
||||
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
|
||||
|
|
@ -3013,19 +2928,10 @@ packages:
|
|||
path-is-absolute: 1.0.1
|
||||
dev: true
|
||||
|
||||
/glob@9.3.5:
|
||||
resolution: {integrity: sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
dependencies:
|
||||
fs.realpath: 1.0.0
|
||||
minimatch: 8.0.4
|
||||
minipass: 4.2.8
|
||||
path-scurry: 1.10.1
|
||||
dev: false
|
||||
|
||||
/globals@11.12.0:
|
||||
resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
|
||||
engines: {node: '>=4'}
|
||||
dev: true
|
||||
|
||||
/globals@13.24.0:
|
||||
resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==}
|
||||
|
|
@ -3075,6 +2981,7 @@ packages:
|
|||
/has-flag@3.0.0:
|
||||
resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
|
||||
engines: {node: '>=4'}
|
||||
dev: true
|
||||
|
||||
/has-flag@4.0.0:
|
||||
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
|
||||
|
|
@ -3131,16 +3038,6 @@ packages:
|
|||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/https-proxy-agent@5.0.1:
|
||||
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
|
||||
engines: {node: '>= 6'}
|
||||
dependencies:
|
||||
agent-base: 6.0.2
|
||||
debug: 4.3.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/human-signals@5.0.0:
|
||||
resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==}
|
||||
engines: {node: '>=16.17.0'}
|
||||
|
|
@ -3243,6 +3140,7 @@ packages:
|
|||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
binary-extensions: 2.3.0
|
||||
dev: true
|
||||
|
||||
/is-boolean-object@1.1.2:
|
||||
resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==}
|
||||
|
|
@ -3291,6 +3189,7 @@ packages:
|
|||
/is-extglob@2.1.1:
|
||||
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/is-finalizationregistry@1.0.2:
|
||||
resolution: {integrity: sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==}
|
||||
|
|
@ -3315,6 +3214,7 @@ packages:
|
|||
engines: {node: '>=0.10.0'}
|
||||
dependencies:
|
||||
is-extglob: 2.1.1
|
||||
dev: true
|
||||
|
||||
/is-hexadecimal@1.0.4:
|
||||
resolution: {integrity: sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==}
|
||||
|
|
@ -3347,6 +3247,7 @@ packages:
|
|||
/is-number@7.0.0:
|
||||
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
||||
engines: {node: '>=0.12.0'}
|
||||
dev: true
|
||||
|
||||
/is-path-inside@3.0.3:
|
||||
resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==}
|
||||
|
|
@ -3429,6 +3330,7 @@ packages:
|
|||
|
||||
/isexe@2.0.0:
|
||||
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
||||
dev: true
|
||||
|
||||
/iterator.prototype@1.1.2:
|
||||
resolution: {integrity: sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==}
|
||||
|
|
@ -3456,6 +3358,7 @@ packages:
|
|||
|
||||
/js-tokens@4.0.0:
|
||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||
dev: true
|
||||
|
||||
/js-yaml@4.1.0:
|
||||
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
|
||||
|
|
@ -3478,6 +3381,7 @@ packages:
|
|||
resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==}
|
||||
engines: {node: '>=4'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/jsesc@3.0.2:
|
||||
resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==}
|
||||
|
|
@ -3505,6 +3409,7 @@ packages:
|
|||
resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
|
||||
engines: {node: '>=6'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/jsonc-eslint-parser@2.4.0:
|
||||
resolution: {integrity: sha512-WYDyuc/uFcGp6YtM2H0uKmUwieOuzeE/5YocFJLnLfclZ4inf3mRn8ZVy1s7Hxji7Jxm6Ss8gqpexD/GlKoGgg==}
|
||||
|
|
@ -3602,6 +3507,7 @@ packages:
|
|||
engines: {node: '>=10'}
|
||||
dependencies:
|
||||
p-locate: 5.0.0
|
||||
dev: true
|
||||
|
||||
/locate-path@7.2.0:
|
||||
resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==}
|
||||
|
|
@ -3621,11 +3527,13 @@ packages:
|
|||
/lru-cache@10.2.0:
|
||||
resolution: {integrity: sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==}
|
||||
engines: {node: 14 || >=16.14}
|
||||
dev: true
|
||||
|
||||
/lru-cache@5.1.1:
|
||||
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
||||
dependencies:
|
||||
yallist: 3.1.1
|
||||
dev: true
|
||||
|
||||
/lru-cache@6.0.0:
|
||||
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
|
||||
|
|
@ -3634,13 +3542,6 @@ packages:
|
|||
yallist: 4.0.0
|
||||
dev: true
|
||||
|
||||
/magic-string@0.27.0:
|
||||
resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.4.15
|
||||
dev: false
|
||||
|
||||
/magic-string@0.30.8:
|
||||
resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
|
@ -3713,13 +3614,6 @@ packages:
|
|||
brace-expansion: 1.1.11
|
||||
dev: true
|
||||
|
||||
/minimatch@8.0.4:
|
||||
resolution: {integrity: sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
dependencies:
|
||||
brace-expansion: 2.0.1
|
||||
dev: false
|
||||
|
||||
/minimatch@9.0.3:
|
||||
resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
|
|
@ -3727,14 +3621,10 @@ packages:
|
|||
brace-expansion: 2.0.1
|
||||
dev: true
|
||||
|
||||
/minipass@4.2.8:
|
||||
resolution: {integrity: sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==}
|
||||
engines: {node: '>=8'}
|
||||
dev: false
|
||||
|
||||
/minipass@7.0.4:
|
||||
resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
dev: true
|
||||
|
||||
/mlly@1.6.1:
|
||||
resolution: {integrity: sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA==}
|
||||
|
|
@ -3747,6 +3637,7 @@ packages:
|
|||
|
||||
/ms@2.1.2:
|
||||
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
|
||||
dev: true
|
||||
|
||||
/ms@2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
|
|
@ -3774,20 +3665,9 @@ packages:
|
|||
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
||||
dev: true
|
||||
|
||||
/node-fetch@2.7.0:
|
||||
resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
|
||||
engines: {node: 4.x || >=6.0.0}
|
||||
peerDependencies:
|
||||
encoding: ^0.1.0
|
||||
peerDependenciesMeta:
|
||||
encoding:
|
||||
optional: true
|
||||
dependencies:
|
||||
whatwg-url: 5.0.0
|
||||
dev: false
|
||||
|
||||
/node-releases@2.0.14:
|
||||
resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==}
|
||||
dev: true
|
||||
|
||||
/normalize-package-data@2.5.0:
|
||||
resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==}
|
||||
|
|
@ -3811,6 +3691,7 @@ packages:
|
|||
/normalize-path@3.0.0:
|
||||
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/normalize-range@0.1.2:
|
||||
resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
|
||||
|
|
@ -3938,6 +3819,7 @@ packages:
|
|||
engines: {node: '>=10'}
|
||||
dependencies:
|
||||
yocto-queue: 0.1.0
|
||||
dev: true
|
||||
|
||||
/p-limit@4.0.0:
|
||||
resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==}
|
||||
|
|
@ -3958,6 +3840,7 @@ packages:
|
|||
engines: {node: '>=10'}
|
||||
dependencies:
|
||||
p-limit: 3.1.0
|
||||
dev: true
|
||||
|
||||
/p-locate@6.0.0:
|
||||
resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==}
|
||||
|
|
@ -4016,6 +3899,7 @@ packages:
|
|||
/path-exists@4.0.0:
|
||||
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/path-exists@5.0.0:
|
||||
resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==}
|
||||
|
|
@ -4047,6 +3931,7 @@ packages:
|
|||
dependencies:
|
||||
lru-cache: 10.2.0
|
||||
minipass: 7.0.4
|
||||
dev: true
|
||||
|
||||
/path-type@4.0.0:
|
||||
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
|
||||
|
|
@ -4059,10 +3944,12 @@ packages:
|
|||
|
||||
/picocolors@1.0.0:
|
||||
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
|
||||
dev: true
|
||||
|
||||
/picomatch@2.3.1:
|
||||
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
|
||||
engines: {node: '>=8.6'}
|
||||
dev: true
|
||||
|
||||
/picomatch@4.0.1:
|
||||
resolution: {integrity: sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg==}
|
||||
|
|
@ -4182,11 +4069,6 @@ packages:
|
|||
engines: {node: '>= 0.8.0'}
|
||||
dev: true
|
||||
|
||||
/progress@2.0.3:
|
||||
resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
dev: false
|
||||
|
||||
/prompts@2.4.2:
|
||||
resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
|
||||
engines: {node: '>= 6'}
|
||||
|
|
@ -4195,10 +4077,6 @@ packages:
|
|||
sisteransi: 1.0.5
|
||||
dev: true
|
||||
|
||||
/proxy-from-env@1.1.0:
|
||||
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
|
||||
dev: false
|
||||
|
||||
/punycode@2.3.1:
|
||||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||
engines: {node: '>=6'}
|
||||
|
|
@ -4258,6 +4136,7 @@ packages:
|
|||
engines: {node: '>=8.10.0'}
|
||||
dependencies:
|
||||
picomatch: 2.3.1
|
||||
dev: true
|
||||
|
||||
/reflect.getprototypeof@1.0.6:
|
||||
resolution: {integrity: sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==}
|
||||
|
|
@ -4394,6 +4273,7 @@ packages:
|
|||
/semver@6.3.1:
|
||||
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/semver@7.6.0:
|
||||
resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==}
|
||||
|
|
@ -4642,6 +4522,7 @@ packages:
|
|||
engines: {node: '>=4'}
|
||||
dependencies:
|
||||
has-flag: 3.0.0
|
||||
dev: true
|
||||
|
||||
/supports-color@7.2.0:
|
||||
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
||||
|
|
@ -4713,12 +4594,14 @@ packages:
|
|||
/to-fast-properties@2.0.0:
|
||||
resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==}
|
||||
engines: {node: '>=4'}
|
||||
dev: true
|
||||
|
||||
/to-regex-range@5.0.1:
|
||||
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
||||
engines: {node: '>=8.0'}
|
||||
dependencies:
|
||||
is-number: 7.0.0
|
||||
dev: true
|
||||
|
||||
/toml-eslint-parser@0.9.3:
|
||||
resolution: {integrity: sha512-moYoCvkNUAPCxSW9jmHmRElhm4tVJpHL8ItC/+uYD0EpPSFXbck7yREz9tNdJVTSpHVod8+HoipcpbQ0oE6gsw==}
|
||||
|
|
@ -4727,10 +4610,6 @@ packages:
|
|||
eslint-visitor-keys: 3.4.3
|
||||
dev: true
|
||||
|
||||
/tr46@0.0.3:
|
||||
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
|
||||
dev: false
|
||||
|
||||
/ts-api-utils@1.3.0(typescript@5.4.3):
|
||||
resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==}
|
||||
engines: {node: '>=16'}
|
||||
|
|
@ -4867,15 +4746,6 @@ packages:
|
|||
'@types/unist': 2.0.10
|
||||
dev: true
|
||||
|
||||
/unplugin@1.0.1:
|
||||
resolution: {integrity: sha512-aqrHaVBWW1JVKBHmGo33T5TxeL0qWzfvjWokObHA9bYmN7eNDkwOxmLjhioHl9878qDFMAaT51XNroRyuz7WxA==}
|
||||
dependencies:
|
||||
acorn: 8.11.3
|
||||
chokidar: 3.6.0
|
||||
webpack-sources: 3.2.3
|
||||
webpack-virtual-modules: 0.5.0
|
||||
dev: false
|
||||
|
||||
/update-browserslist-db@1.0.13(browserslist@4.23.0):
|
||||
resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==}
|
||||
hasBin: true
|
||||
|
|
@ -4885,6 +4755,7 @@ packages:
|
|||
browserslist: 4.23.0
|
||||
escalade: 3.1.2
|
||||
picocolors: 1.0.0
|
||||
dev: true
|
||||
|
||||
/uri-js@4.4.1:
|
||||
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
||||
|
|
@ -5000,26 +4871,6 @@ packages:
|
|||
- supports-color
|
||||
dev: true
|
||||
|
||||
/webidl-conversions@3.0.1:
|
||||
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
|
||||
dev: false
|
||||
|
||||
/webpack-sources@3.2.3:
|
||||
resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
dev: false
|
||||
|
||||
/webpack-virtual-modules@0.5.0:
|
||||
resolution: {integrity: sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==}
|
||||
dev: false
|
||||
|
||||
/whatwg-url@5.0.0:
|
||||
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
|
||||
dependencies:
|
||||
tr46: 0.0.3
|
||||
webidl-conversions: 3.0.1
|
||||
dev: false
|
||||
|
||||
/which-boxed-primitive@1.0.2:
|
||||
resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==}
|
||||
dependencies:
|
||||
|
|
@ -5075,6 +4926,7 @@ packages:
|
|||
hasBin: true
|
||||
dependencies:
|
||||
isexe: 2.0.0
|
||||
dev: true
|
||||
|
||||
/wrap-ansi@7.0.0:
|
||||
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
|
||||
|
|
@ -5105,6 +4957,7 @@ packages:
|
|||
|
||||
/yallist@3.1.1:
|
||||
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
|
||||
dev: true
|
||||
|
||||
/yallist@4.0.0:
|
||||
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
|
||||
|
|
@ -5128,6 +4981,7 @@ packages:
|
|||
/yocto-queue@0.1.0:
|
||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
/yocto-queue@1.0.0:
|
||||
resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ authors = { workspace = true }
|
|||
tauri-build = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
onelauncher = { path = "../onelauncher", features = [ "tauri" ] }
|
||||
onelauncher = { path = "../onelauncher" }
|
||||
|
||||
interpulse = { workspace = true, features = [ "bincode" ] }
|
||||
|
||||
|
|
|
|||
|
|
@ -6,9 +6,10 @@ fn main() {
|
|||
InlinedPlugin::new().commands(&[
|
||||
"login_msa",
|
||||
"refresh_client_manager",
|
||||
"create_instance",
|
||||
"get_instances",
|
||||
"get_instance",
|
||||
"launch_cluster",
|
||||
"create_cluster",
|
||||
"get_clusters",
|
||||
"get_cluster",
|
||||
"get_manifest",
|
||||
]),
|
||||
));
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@
|
|||
description = "Default permissions for the plugin"
|
||||
permissions = [
|
||||
"allow-refresh-client-manager",
|
||||
"allow-create-instance",
|
||||
"allow-get-instances",
|
||||
"allow-get-instance",
|
||||
"allow-launch-cluster",
|
||||
"allow-create-cluster",
|
||||
"allow-get-clusters",
|
||||
"allow-get-cluster",
|
||||
"allow-get-manifest",
|
||||
"allow-login-msa",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use launcher_core::auth::{
|
||||
use onelauncher::auth::{
|
||||
microsoft_auth::MicrosoftAuthenticationMethod, Account, AuthenticationMethod,
|
||||
};
|
||||
use tauri::Manager;
|
||||
|
|
|
|||
|
|
@ -1,50 +1,68 @@
|
|||
use launcher_core::game::client::{ClientType, Instance, Manifest};
|
||||
use tauri::State;
|
||||
use onelauncher::{game::{client::LaunchCallbacks, client_manager::ClusterWithManifest, clients::ClientType}, AppState};
|
||||
use tauri::{Manager, State};
|
||||
use tokio::sync::Mutex;
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::GameManagerState;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn create_instance(
|
||||
state: State<'_, Mutex<GameManagerState>>,
|
||||
pub async fn create_cluster(
|
||||
state: State<'_, Mutex<AppState>>,
|
||||
name: String,
|
||||
version: String,
|
||||
client: ClientType,
|
||||
cover: Option<String>,
|
||||
group: Option<Uuid>,
|
||||
) -> Result<Uuid, String> {
|
||||
let manager = &mut state.lock().await.client_manager;
|
||||
let manager = &mut state.lock().await.clients;
|
||||
let uuid = manager
|
||||
.create_instance(name, version, cover, group, client)
|
||||
.create_cluster(name, version, cover, group, client)
|
||||
.await?;
|
||||
Ok(uuid)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_instances(
|
||||
state: State<'_, Mutex<GameManagerState>>,
|
||||
) -> Result<Vec<Instance>, String> {
|
||||
let manager = &mut state.lock().await.client_manager;
|
||||
Ok(manager.get_instances_owned())
|
||||
pub async fn get_clusters(
|
||||
state: State<'_, Mutex<AppState>>,
|
||||
) -> Result<Vec<ClusterWithManifest>, String> {
|
||||
let manager = &mut state.lock().await.clients;
|
||||
Ok(manager.get_clusters_owned())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_instance(
|
||||
state: State<'_, Mutex<GameManagerState>>,
|
||||
pub async fn get_cluster(
|
||||
state: State<'_, Mutex<AppState>>,
|
||||
uuid: Uuid,
|
||||
) -> Result<Instance, String> {
|
||||
let manager = &mut state.lock().await.client_manager;
|
||||
let instance = manager.get_instance(uuid)?;
|
||||
Ok(instance.clone())
|
||||
) -> Result<ClusterWithManifest, String> {
|
||||
let manager = &mut state.lock().await.clients;
|
||||
let cluster = manager.get_cluster(uuid)?;
|
||||
Ok(cluster.clone())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_manifest(
|
||||
state: State<'_, Mutex<GameManagerState>>,
|
||||
pub async fn launch_cluster(
|
||||
app: tauri::AppHandle,
|
||||
state: State<'_, Mutex<AppState>>,
|
||||
uuid: Uuid,
|
||||
) -> Result<Manifest, String> {
|
||||
let manager = &mut state.lock().await.client_manager;
|
||||
let manifest = manager.get_manifest(uuid)?;
|
||||
Ok(manifest.clone())
|
||||
) -> Result<i32, String> {
|
||||
let manager = &mut state.lock().await.clients;
|
||||
|
||||
let on_launch_app = app.clone();
|
||||
let on_stdout_app = app.clone();
|
||||
let on_stderr_app = app.clone();
|
||||
|
||||
let callbacks = LaunchCallbacks {
|
||||
on_launch: Box::new(move |pid| {
|
||||
on_launch_app.emit("game:launch", pid).expect("Failed to emit game:launched");
|
||||
}),
|
||||
|
||||
on_stdout: Box::new(move |line| {
|
||||
on_stdout_app.emit("game:stdout", line).expect("Failed to emit game:stdout");
|
||||
}),
|
||||
|
||||
on_stderr: Box::new(move |line| {
|
||||
on_stderr_app.emit("game:stderr", line).expect("Failed to emit game:stderr");
|
||||
})
|
||||
};
|
||||
|
||||
let exit_code = manager.launch_cluster(uuid, callbacks).await?;
|
||||
Ok(exit_code)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,32 +1,23 @@
|
|||
use launcher_core::game::client::ClientManager;
|
||||
use onelauncher::{game::client_manager::ClientManager, AppState};
|
||||
use tauri::{plugin::TauriPlugin, Manager, State};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
mod auth;
|
||||
mod game;
|
||||
|
||||
pub struct GameManagerState {
|
||||
pub client_manager: ClientManager,
|
||||
}
|
||||
|
||||
unsafe impl Send for GameManagerState {}
|
||||
unsafe impl Sync for GameManagerState {}
|
||||
|
||||
pub fn init() -> TauriPlugin<tauri::Wry> {
|
||||
tauri::plugin::Builder::new("onelauncher")
|
||||
.setup(|app, _| {
|
||||
app.manage(Mutex::new(GameManagerState {
|
||||
client_manager: ClientManager::new().expect("Failed to initialize client manager"),
|
||||
}));
|
||||
app.manage(Mutex::new(AppState::new()?));
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
auth::login_msa,
|
||||
game::create_instance,
|
||||
game::get_instances,
|
||||
game::get_instance,
|
||||
game::get_manifest,
|
||||
game::create_cluster,
|
||||
game::get_clusters,
|
||||
game::get_cluster,
|
||||
game::launch_cluster,
|
||||
refresh_client_manager,
|
||||
])
|
||||
.build()
|
||||
|
|
@ -34,8 +25,8 @@ pub fn init() -> TauriPlugin<tauri::Wry> {
|
|||
|
||||
#[tauri::command]
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn refresh_client_manager(state: State<'_, Mutex<GameManagerState>>) -> Result<(), String> {
|
||||
async fn refresh_client_manager(state: State<'_, Mutex<AppState>>) -> Result<(), String> {
|
||||
let mut state = state.lock().await;
|
||||
state.client_manager = ClientManager::new()?;
|
||||
state.clients = ClientManager::new()?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use tracing_error::ExtractSpanTrace;
|
||||
|
||||
pub fn display_tracing_error(err: &launcher_core::Error) {
|
||||
pub fn display_tracing_error(err: &onelauncher::Error) {
|
||||
match get_span_trace(err) {
|
||||
Some(span_trace) => {
|
||||
tracing::error!(error = %err, span_trace = %span_trace);
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
|
||||
use std::error::Error;
|
||||
|
||||
use polyfrost_launcher::api;
|
||||
use tauri::{menu::Menu, App};
|
||||
use onelauncher_gui::api;
|
||||
use tauri::{menu::Menu, App, Manager};
|
||||
|
||||
#[derive(Clone, serde::Serialize)]
|
||||
struct Payload {
|
||||
|
|
@ -14,7 +14,7 @@ struct Payload {
|
|||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let _log_guard = launcher_core::start_logger();
|
||||
let _log_guard = onelauncher::start_logger();
|
||||
tracing::info!("initialized tracing subscriber. loading OneLauncher");
|
||||
|
||||
tauri::Builder::default()
|
||||
|
|
@ -22,7 +22,7 @@ async fn main() {
|
|||
.plugin(tauri_plugin_single_instance::init(|app, argv, cwd| {
|
||||
println!("{}, {argv:?}, {cwd}", app.package_info().name);
|
||||
|
||||
app.emit_all("single-instance", Payload { args: argv, cwd })
|
||||
app.emit("single-instance", Payload { args: argv, cwd })
|
||||
.unwrap();
|
||||
}))
|
||||
.plugin(tauri_plugin_window_state::Builder::default().build())
|
||||
|
|
|
|||
|
|
@ -19,8 +19,3 @@ export async function getUuidHeadSrc(uuid: string): Promise<string> {
|
|||
// TODO implement some kind of caching + fallback
|
||||
return `https://crafatar.com/avatars/${uuid}?size=32`;
|
||||
}
|
||||
|
||||
export default {
|
||||
loginMicrosoft,
|
||||
getUuidHeadSrc,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,33 +1,23 @@
|
|||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { listen } from '@tauri-apps/api/event';
|
||||
|
||||
export async function getInstance(uuid: string): Promise<Core.Instance> {
|
||||
return await invoke<Core.Instance>('plugin:onelauncher|get_instance', {
|
||||
uuid,
|
||||
});
|
||||
export async function getCluster(uuid: string): Promise<Core.ClusterWithManifest> {
|
||||
return await invoke<Core.ClusterWithManifest>(
|
||||
'plugin:onelauncher|get_cluster',
|
||||
{ uuid },
|
||||
);
|
||||
}
|
||||
|
||||
export async function getManifest(uuid: string): Promise<Core.Manifest> {
|
||||
return await invoke<Core.Manifest>('plugin:onelauncher|get_manifest', {
|
||||
uuid,
|
||||
});
|
||||
}
|
||||
export async function getClustersGrouped(): Promise<Map<string, WithIndex<Core.ClusterWithManifest>[]>> {
|
||||
const clusters = await getClusters();
|
||||
const map = new Map<string, WithIndex<Core.ClusterWithManifest>[]>();
|
||||
|
||||
export async function getInstancesWithManifests(): Promise<Core.InstanceWithManifest[]> {
|
||||
const instances = await getInstances();
|
||||
|
||||
return Promise.all(instances.map(async instance => ({
|
||||
instance,
|
||||
manifest: await getManifest(instance.id),
|
||||
})));
|
||||
}
|
||||
|
||||
export async function getGroupedInstances(): Promise<Map<string, Core.InstanceWithManifest[]>> {
|
||||
const instances = await getInstancesWithManifests();
|
||||
const map = new Map<string, Core.InstanceWithManifest[]>();
|
||||
|
||||
instances.forEach((instance, index) => {
|
||||
const groupName = instance.instance.group || 'Unnamed';
|
||||
const value = { ...instance, index };
|
||||
clusters.forEach((cluster, index) => {
|
||||
const groupName = cluster.cluster.group || 'Unnamed';
|
||||
const value: WithIndex<Core.ClusterWithManifest> = {
|
||||
...cluster,
|
||||
index,
|
||||
};
|
||||
|
||||
if (map.has(groupName))
|
||||
map.get(groupName)!.push(value);
|
||||
|
|
@ -38,24 +28,19 @@ export async function getGroupedInstances(): Promise<Map<string, Core.InstanceWi
|
|||
return map;
|
||||
}
|
||||
|
||||
export async function getInstances(): Promise<Core.Instance[]> {
|
||||
return await invoke<Core.Instance[]>('plugin:onelauncher|get_instances');
|
||||
export async function getClusters(): Promise<Core.ClusterWithManifest[]> {
|
||||
return await invoke<Core.ClusterWithManifest[]>('plugin:onelauncher|get_clusters');
|
||||
}
|
||||
|
||||
export async function createInstance(instance: Omit<Core.Instance, 'id' | 'createdAt'> & { version: string }): Promise<void> {
|
||||
export async function createCluster(cluster: Omit<Core.Cluster, 'id' | 'createdAt'> & { version: string }): Promise<void> {
|
||||
return await invoke(
|
||||
'plugin:onelauncher|create_instance',
|
||||
'plugin:onelauncher|create_cluster',
|
||||
{
|
||||
name: instance.name,
|
||||
version: instance.version,
|
||||
client: instance.client,
|
||||
// cover: instance.cover,
|
||||
// group: instance.group,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Accept: 'text/plain',
|
||||
},
|
||||
name: cluster.name,
|
||||
version: cluster.version,
|
||||
client: cluster.client,
|
||||
// cover: cluster.cover,
|
||||
// group: cluster.group,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
@ -63,3 +48,26 @@ export async function createInstance(instance: Omit<Core.Instance, 'id' | 'creat
|
|||
export async function refreshClientManager(): Promise<void> {
|
||||
return await invoke('plugin:onelauncher|refresh_client_manager');
|
||||
}
|
||||
|
||||
interface LaunchCallbacks {
|
||||
on_launch: (pid: number) => any;
|
||||
on_stdout: (line: string) => any;
|
||||
on_stderr: (line: string) => any;
|
||||
};
|
||||
|
||||
export async function launchCluster(uuid: string, callbacks: LaunchCallbacks): Promise<number> {
|
||||
const unlisten_launch = await listen<number>('game:launch', e => callbacks.on_launch(e.payload));
|
||||
const unlisten_stdout = await listen<string>('game:stdout', e => callbacks.on_stdout(e.payload));
|
||||
const unlisten_stderr = await listen<string>('game:stderr', e => callbacks.on_stderr(e.payload));
|
||||
|
||||
const exit_code = await invoke<number>(
|
||||
'plugin:onelauncher|launch_cluster',
|
||||
{ uuid },
|
||||
);
|
||||
|
||||
unlisten_launch();
|
||||
unlisten_stdout();
|
||||
unlisten_stderr();
|
||||
|
||||
return exit_code;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,18 +8,18 @@ import App from './ui/App';
|
|||
import HomePage from './ui/pages/Home';
|
||||
import BrowserPage from './ui/pages/Browser';
|
||||
import UpdatesPage from './ui/pages/Updates';
|
||||
import InstancePage from './ui/pages/instances';
|
||||
import InstanceOverview from './ui/pages/instances/InstanceOverview';
|
||||
import InstanceMods from './ui/pages/instances/InstanceMods';
|
||||
import ClusterPage from './ui/pages/cluster';
|
||||
import ClusterOverview from './ui/pages/cluster/ClusterOverview';
|
||||
import ClusterMods from './ui/pages/cluster/ClusterMods';
|
||||
|
||||
render(() => (
|
||||
<Router root={App}>
|
||||
<Route path="/" component={HomePage} />
|
||||
<Route path="/browser" component={BrowserPage} />
|
||||
<Route path="/updates" component={UpdatesPage} />
|
||||
<Route path="/instances" component={InstancePage}>
|
||||
<Route path="/" component={InstanceOverview} />
|
||||
<Route path="/mods" component={InstanceMods} />
|
||||
<Route path="/clusters" component={ClusterPage}>
|
||||
<Route path="/" component={ClusterOverview} />
|
||||
<Route path="/mods" component={ClusterMods} />
|
||||
</Route>
|
||||
</Router>
|
||||
), document.getElementById('root') as HTMLElement);
|
||||
|
|
|
|||
8
src/types/game.d.ts
vendored
8
src/types/game.d.ts
vendored
|
|
@ -1,7 +1,7 @@
|
|||
declare namespace Core {
|
||||
export interface InstanceWithManifest {
|
||||
instance: Instance,
|
||||
manifest: Manifest,
|
||||
export type ClusterWithManifest<T extends keyof ClientType = keyof ClientType> = {
|
||||
cluster: Cluster<T>,
|
||||
manifest: Manifest
|
||||
}
|
||||
|
||||
export interface Manifest {
|
||||
|
|
@ -16,7 +16,7 @@ declare namespace Core {
|
|||
},
|
||||
}
|
||||
|
||||
export interface Instance<T extends keyof ClientType = keyof ClientType> {
|
||||
export interface Cluster<T extends keyof ClientType = keyof ClientType> {
|
||||
id: string,
|
||||
createdAt: number,
|
||||
name: string,
|
||||
|
|
|
|||
3
src/types/global.d.ts
vendored
Normal file
3
src/types/global.d.ts
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
declare namespace globalThis {
|
||||
export type WithIndex<T> = T & { index: number }
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { createResource } from 'solid-js';
|
||||
import type { JSX } from 'solid-js';
|
||||
import auth from '../../../bridge/auth';
|
||||
import * as auth from '../../../bridge/auth';
|
||||
import steveSrc from '../../../assets/images/steve.png';
|
||||
|
||||
type PlayerHeadProps = JSX.IntrinsicElements['img'] & { uuid: string };
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import {
|
||||
CheckIcon,
|
||||
DotsVerticalIcon,
|
||||
Download01Icon,
|
||||
EyeIcon,
|
||||
PlayIcon,
|
||||
PlusIcon,
|
||||
|
|
@ -11,13 +10,12 @@ import type { Accessor, Setter } from 'solid-js';
|
|||
import { For, Show, createResource, createSignal } from 'solid-js';
|
||||
import { useNavigate } from '@solidjs/router';
|
||||
import image from '../../assets/images/header.png';
|
||||
import defaultCover from '../../assets/images/default_instance_cover.jpg';
|
||||
import ContextMenu from '../components/overlay/ContextMenu';
|
||||
import { createCluster, getClustersGrouped, launchCluster } from '../../bridge/game';
|
||||
import Button from '../components/base/Button';
|
||||
import Tag from '../components/base/Tag';
|
||||
import TextField from '../components/base/TextField';
|
||||
import defaultCover from '../../assets/images/default_instance_cover.jpg';
|
||||
import ContextMenu from '../components/overlay/ContextMenu';
|
||||
import { createInstance, getGroupedInstances, getInstancesWithManifests } from '../../bridge/game';
|
||||
import Modal from '../components/overlay/Modal';
|
||||
|
||||
// TODO: Replace this into it's own component
|
||||
// possibly with the core ui lirbary with autogenerated and optimized svgs pls
|
||||
|
|
@ -63,16 +61,30 @@ function Banner() {
|
|||
);
|
||||
}
|
||||
|
||||
interface InstanceCardContextMenuProps {
|
||||
interface ClusterCardContextMenuProps {
|
||||
pos: Accessor<{ x: number; y: number }>;
|
||||
contextMenuVisible: Accessor<boolean>;
|
||||
setContextMenuVisible: Setter<boolean>;
|
||||
id: string;
|
||||
}
|
||||
|
||||
function InstanceCardContextMenu(props: InstanceCardContextMenuProps) {
|
||||
function ClusterCardContextMenu(props: ClusterCardContextMenuProps) {
|
||||
const navigate = useNavigate();
|
||||
|
||||
function viewCluster() {
|
||||
navigate(`/clusters/?id=${props.id}`);
|
||||
}
|
||||
|
||||
async function launch() {
|
||||
const exit_code = await launchCluster(props.id, {
|
||||
on_launch: pid => console.log('Launched with PID:', pid),
|
||||
on_stdout: line => console.log('STDOUT:', line),
|
||||
on_stderr: line => console.log('STDERR:', line),
|
||||
});
|
||||
|
||||
console.log('Exit with code: ', exit_code);
|
||||
}
|
||||
|
||||
return (
|
||||
<ContextMenu
|
||||
pos={props.pos}
|
||||
|
|
@ -82,9 +94,7 @@ function InstanceCardContextMenu(props: InstanceCardContextMenuProps) {
|
|||
<ContextMenu.Row
|
||||
icon={<EyeIcon />}
|
||||
text="View"
|
||||
onClick={() => {
|
||||
navigate(`/instances/?id=${props.id}`);
|
||||
}}
|
||||
onClick={() => viewCluster()}
|
||||
/>
|
||||
|
||||
<ContextMenu.Seperator />
|
||||
|
|
@ -92,28 +102,20 @@ function InstanceCardContextMenu(props: InstanceCardContextMenuProps) {
|
|||
<ContextMenu.Row
|
||||
icon={<PlayIcon />}
|
||||
text="Launch"
|
||||
onClick={() => launch()}
|
||||
/>
|
||||
</ContextMenu>
|
||||
);
|
||||
}
|
||||
|
||||
declare module 'solid-js' {
|
||||
// eslint-disable-next-line ts/no-namespace
|
||||
namespace JSX {
|
||||
interface Directives {
|
||||
sortable: any;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function InstanceCard(props: Core.InstanceWithManifest) {
|
||||
function ClusterCard(props: Core.ClusterWithManifest) {
|
||||
const navigate = useNavigate();
|
||||
const [pos, setPos] = createSignal({ x: 0, y: 0 });
|
||||
const [contextMenuVisible, setContextMenuVisible] = createSignal(false);
|
||||
let ref!: HTMLDivElement;
|
||||
|
||||
function openInstancePage(_e: MouseEvent) {
|
||||
navigate(`/instances/?id=${props.instance.id}`);
|
||||
function openClusterPage(_e: MouseEvent) {
|
||||
navigate(`/clusters/?id=${props.cluster.id}`);
|
||||
}
|
||||
|
||||
function openContextMenu(e: MouseEvent) {
|
||||
|
|
@ -129,7 +131,7 @@ function InstanceCard(props: Core.InstanceWithManifest) {
|
|||
return (
|
||||
<>
|
||||
<div
|
||||
onClick={e => openInstancePage(e)}
|
||||
onClick={e => openClusterPage(e)}
|
||||
onMouseDown={e => onMouseDown(e)}
|
||||
onContextMenu={e => openContextMenu(e)}
|
||||
ref={ref}
|
||||
|
|
@ -141,16 +143,16 @@ function InstanceCard(props: Core.InstanceWithManifest) {
|
|||
style={{ '-webkit-transform': 'translateZ(0)' }}
|
||||
>
|
||||
<img
|
||||
class={`object-cover h-full w-full ${props.instance.cover ? '' : ' filter grayscale'}`}
|
||||
src={props.instance.cover || defaultCover}
|
||||
class={`object-cover h-full w-full ${props.cluster.cover ? '' : ' filter grayscale'}`}
|
||||
src={props.cluster.cover || defaultCover}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="z-10 p-3 flex flex-row items-center justify-between">
|
||||
<div class="flex flex-col gap-1.5">
|
||||
<p class="h-4 font-semibold">{props.instance.name}</p>
|
||||
<p class="h-4 font-semibold">{props.cluster.name}</p>
|
||||
<p class="h-4 text-xs">
|
||||
{props.instance.client.type}
|
||||
{props.cluster.client.type}
|
||||
{' '}
|
||||
{props.manifest.manifest.id}
|
||||
{/* {' '}
|
||||
|
|
@ -162,39 +164,40 @@ function InstanceCard(props: Core.InstanceWithManifest) {
|
|||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<InstanceCardContextMenu
|
||||
|
||||
<ClusterCardContextMenu
|
||||
contextMenuVisible={contextMenuVisible}
|
||||
setContextMenuVisible={setContextMenuVisible}
|
||||
pos={pos}
|
||||
id={props.instance.id}
|
||||
id={props.cluster.id}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
interface InstanceGroupProps {
|
||||
interface ClusterGroupProps {
|
||||
title: string;
|
||||
instances: Core.InstanceWithManifest[];
|
||||
clusters: Core.ClusterWithManifest[];
|
||||
}
|
||||
|
||||
function InstanceGroup(props: InstanceGroupProps) {
|
||||
function ClusterGroup(props: ClusterGroupProps) {
|
||||
return (
|
||||
<div class="flex flex-col gap-y-4">
|
||||
<h4>{props.title}</h4>
|
||||
<div class="grid grid-cols-4 2xl:grid-cols-6 gap-4">
|
||||
<For each={props.instances}>{item => <InstanceCard {...item} />}</For>
|
||||
<For each={props.clusters}>{item => <ClusterCard {...item} />}</For>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function HomePage() {
|
||||
const [instances, { refetch }] = createResource(getGroupedInstances);
|
||||
const [clusters, { refetch }] = createResource(getClustersGrouped);
|
||||
|
||||
async function newInstance() {
|
||||
await createInstance({
|
||||
name: 'New Instance',
|
||||
version: '1.8.9',
|
||||
await createCluster({
|
||||
name: '1.0 cluster',
|
||||
version: 'a1.0.11',
|
||||
client: {
|
||||
type: 'Vanilla',
|
||||
},
|
||||
|
|
@ -217,22 +220,22 @@ function HomePage() {
|
|||
<Button
|
||||
styleType="primary"
|
||||
iconLeft={<PlusIcon class="!w-5 stroke-[2.2]" />}
|
||||
children="New Instance"
|
||||
children="New Cluster"
|
||||
onClick={() => newInstance()}
|
||||
/>
|
||||
|
||||
<Button
|
||||
{/* <Button
|
||||
styleType="secondary"
|
||||
iconLeft={<Download01Icon />}
|
||||
children="From URL"
|
||||
/>
|
||||
/> */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Show when={instances() !== undefined}>
|
||||
<For each={Array.from(instances()!.entries())}>
|
||||
{([title, instances]) => (
|
||||
<InstanceGroup title={title} instances={instances} />
|
||||
<Show when={clusters() !== undefined}>
|
||||
<For each={Array.from(clusters()!.entries())}>
|
||||
{([title, clusters]) => (
|
||||
<ClusterGroup title={title} clusters={clusters} />
|
||||
)}
|
||||
</For>
|
||||
</Show>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
function InstanceMods() {
|
||||
function ClusterMods() {
|
||||
return (
|
||||
<div>
|
||||
<h1>This is the Mods page</h1>
|
||||
|
|
@ -6,4 +6,4 @@ function InstanceMods() {
|
|||
);
|
||||
}
|
||||
|
||||
export default InstanceMods;
|
||||
export default ClusterMods;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
function InstanceOverview() {
|
||||
function ClusterOverview() {
|
||||
return (
|
||||
<div>
|
||||
<h1>This is the overview</h1>
|
||||
|
|
@ -6,4 +6,4 @@ function InstanceOverview() {
|
|||
);
|
||||
}
|
||||
|
||||
export default InstanceOverview;
|
||||
export default ClusterOverview;
|
||||
|
|
@ -10,7 +10,7 @@ function Link(props: AnchorProps) {
|
|||
);
|
||||
}
|
||||
|
||||
function InstancePage(props: ParentProps) {
|
||||
function ClusterPage(props: ParentProps) {
|
||||
const [searchParams] = useSearchParams();
|
||||
const navigate = useNavigate();
|
||||
|
||||
|
|
@ -22,8 +22,8 @@ function InstancePage(props: ParentProps) {
|
|||
return (
|
||||
<div>
|
||||
<div>
|
||||
<Link href="/instances/">Overview</Link>
|
||||
<Link href="/instances/mods">mods</Link>
|
||||
<Link href="/clusters/">Overview</Link>
|
||||
<Link href="/clusters/mods">mods</Link>
|
||||
</div>
|
||||
{props.children}
|
||||
{searchParams.id}
|
||||
|
|
@ -31,4 +31,4 @@ function InstancePage(props: ParentProps) {
|
|||
);
|
||||
}
|
||||
|
||||
export default InstancePage;
|
||||
export default ClusterPage;
|
||||
Loading…
Add table
Reference in a new issue