diff --git a/Cargo.lock b/Cargo.lock index f4262236de..515e37fbfa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4416,6 +4416,7 @@ dependencies = [ "schemars 0.8.22", "serde", "serde_json", + "tracing", "url", "uuid", ] @@ -4478,6 +4479,7 @@ dependencies = [ "rivet-pools", "rivet-runner-protocol", "rivet-runtime", + "rivet-serverless-backfill", "rivet-service-manager", "rivet-telemetry", "rivet-term", @@ -4739,6 +4741,19 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "rivet-serverless-backfill" +version = "0.1.0" +dependencies = [ + "anyhow", + "gasoline", + "pegboard", + "rivet-config", + "rivet-types", + "tracing", + "universaldb", +] + [[package]] name = "rivet-service-manager" version = "2.0.25" diff --git a/Cargo.toml b/Cargo.toml index 5ea0101e0d..4d31be8acb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [workspace] resolver = "2" -members = ["engine/packages/actor-kv","engine/packages/api-builder","engine/packages/api-peer","engine/packages/api-public","engine/packages/api-types","engine/packages/api-util","engine/packages/bootstrap","engine/packages/cache","engine/packages/cache-purge","engine/packages/cache-result","engine/packages/clickhouse-inserter","engine/packages/clickhouse-user-query","engine/packages/config","engine/packages/dump-openapi","engine/packages/engine","engine/packages/env","engine/packages/epoxy","engine/packages/error","engine/packages/error-macros","engine/packages/gasoline","engine/packages/gasoline-macros","engine/packages/guard","engine/packages/guard-core","engine/packages/logs","engine/packages/metrics","engine/packages/namespace","engine/packages/pegboard","engine/packages/pegboard-gateway","engine/packages/pegboard-runner","engine/packages/pools","engine/packages/runtime","engine/packages/service-manager","engine/packages/telemetry","engine/packages/test-deps","engine/packages/test-deps-docker","engine/packages/tracing-reconfigure","engine/packages/types","engine/packages/universaldb","engine/packages/universalpubsub","engine/packages/util","engine/packages/util-id","engine/packages/workflow-worker","engine/sdks/rust/api-full","engine/sdks/rust/data","engine/sdks/rust/epoxy-protocol","engine/sdks/rust/runner-protocol","engine/sdks/rust/ups-protocol"] +members = ["engine/packages/actor-kv","engine/packages/api-builder","engine/packages/api-peer","engine/packages/api-public","engine/packages/api-types","engine/packages/api-util","engine/packages/bootstrap","engine/packages/cache","engine/packages/cache-purge","engine/packages/cache-result","engine/packages/clickhouse-inserter","engine/packages/clickhouse-user-query","engine/packages/config","engine/packages/dump-openapi","engine/packages/engine","engine/packages/env","engine/packages/epoxy","engine/packages/error","engine/packages/error-macros","engine/packages/gasoline","engine/packages/gasoline-macros","engine/packages/guard","engine/packages/guard-core","engine/packages/logs","engine/packages/metrics","engine/packages/namespace","engine/packages/pegboard","engine/packages/pegboard-gateway","engine/packages/pegboard-runner","engine/packages/pools","engine/packages/runtime","engine/packages/serverless-backfill","engine/packages/service-manager","engine/packages/telemetry","engine/packages/test-deps","engine/packages/test-deps-docker","engine/packages/tracing-reconfigure","engine/packages/tracing-utils","engine/packages/types","engine/packages/universaldb","engine/packages/universalpubsub","engine/packages/util","engine/packages/util-id","engine/packages/workflow-worker","engine/sdks/rust/api-full","engine/sdks/rust/data","engine/sdks/rust/epoxy-protocol","engine/sdks/rust/runner-protocol","engine/sdks/rust/ups-protocol"] [workspace.package] version = "2.0.25" @@ -83,10 +83,13 @@ tracing = "0.1.40" tracing-core = "0.1" tracing-opentelemetry = "0.29" tracing-slog = "0.2" -vergen = { version = "9.0.4", features = ["build", "cargo", "rustc"] } vergen-gitcl = "1.0.0" reqwest-eventsource = "0.6.0" +[workspace.dependencies.vergen] +version = "9.0.4" +features = ["build","cargo","rustc"] + [workspace.dependencies.sentry] version = "0.45.0" default-features = false @@ -148,7 +151,7 @@ features = ["now"] [workspace.dependencies.clap] version = "4.3" -features = ["derive", "cargo"] +features = ["derive","cargo"] [workspace.dependencies.rivet-term] git = "https://github.com/rivet-dev/rivet-term" @@ -357,6 +360,9 @@ path = "engine/packages/pools" [workspace.dependencies.rivet-runtime] path = "engine/packages/runtime" +[workspace.dependencies.rivet-serverless-backfill] +path = "engine/packages/serverless-backfill" + [workspace.dependencies.rivet-service-manager] path = "engine/packages/service-manager" @@ -425,8 +431,3 @@ debug = false lto = "fat" codegen-units = 1 opt-level = 3 - -# strip = true -# panic = "abort" -# overflow-checks = false -# debug-assertions = false diff --git a/engine/packages/api-builder/src/global_context.rs b/engine/packages/api-builder/src/global_context.rs index 62c17f366d..bac1f6aa07 100644 --- a/engine/packages/api-builder/src/global_context.rs +++ b/engine/packages/api-builder/src/global_context.rs @@ -18,7 +18,7 @@ impl GlobalApiCtx { name: &'static str, ) -> Result { let cache = rivet_cache::CacheInner::from_env(&config, pools.clone())?; - let db = gas::prelude::db::DatabaseKv::from_pools(pools.clone()).await?; + let db = gas::prelude::db::DatabaseKv::new(config.clone(), pools.clone()).await?; Ok(Self { db, diff --git a/engine/packages/bootstrap/src/lib.rs b/engine/packages/bootstrap/src/lib.rs index 35088c2d30..f74f8624a6 100644 --- a/engine/packages/bootstrap/src/lib.rs +++ b/engine/packages/bootstrap/src/lib.rs @@ -3,7 +3,7 @@ use gas::prelude::*; pub async fn start(config: rivet_config::Config, pools: rivet_pools::Pools) -> Result<()> { let cache = rivet_cache::CacheInner::from_env(&config, pools.clone())?; let ctx = StandaloneCtx::new( - db::DatabaseKv::from_pools(pools.clone()).await?, + db::DatabaseKv::new(config.clone(), pools.clone()).await?, config.clone(), pools, cache, diff --git a/engine/packages/config/Cargo.toml b/engine/packages/config/Cargo.toml index ddfee56334..7e3936ea49 100644 --- a/engine/packages/config/Cargo.toml +++ b/engine/packages/config/Cargo.toml @@ -18,3 +18,4 @@ serde_json.workspace = true serde.workspace = true url.workspace = true uuid.workspace = true +tracing.workspace = true diff --git a/engine/packages/config/src/config/pegboard.rs b/engine/packages/config/src/config/pegboard.rs index 308d807bb7..97eeda02a6 100644 --- a/engine/packages/config/src/config/pegboard.rs +++ b/engine/packages/config/src/config/pegboard.rs @@ -75,6 +75,9 @@ pub struct Pegboard { /// /// **Experimental** pub serverless_backoff_max_exponent: Option, + + /// Global pool desired max. + pub pool_desired_max_override: Option, } impl Pegboard { diff --git a/engine/packages/config/src/config/runtime.rs b/engine/packages/config/src/config/runtime.rs index 489b95507d..cde4350bc2 100644 --- a/engine/packages/config/src/config/runtime.rs +++ b/engine/packages/config/src/config/runtime.rs @@ -5,6 +5,9 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)] pub struct Runtime { + /// Adjusts worker curve around this value (in millicores, i.e. 1000 = 1 core). Is not a hard limit. When + /// unset, uses /sys/fs/cgroup/cpu.max, and if that is unset uses total host cpu. + pub worker_cpu_max: Option, /// Time (in seconds) to allow for the gasoline worker engine to stop gracefully after receiving SIGTERM. /// Defaults to 30 seconds. worker_shutdown_duration: Option, diff --git a/engine/packages/config/src/lib.rs b/engine/packages/config/src/lib.rs index 3ac5186e28..93cf30f2b7 100644 --- a/engine/packages/config/src/lib.rs +++ b/engine/packages/config/src/lib.rs @@ -79,11 +79,15 @@ fn add_source>( let path = path.as_ref(); if !path.exists() { + tracing::debug!(path=%path.display(), "ignoring non-existent config path"); + // Silently ignore non-existent paths return Ok(settings); } if path.is_dir() { + tracing::debug!(path=%path.display(), "loading config from directory"); + for entry in std::fs::read_dir(path)? { let entry = entry?; let path = entry.path(); @@ -96,9 +100,14 @@ fn add_source>( } } } else if path.is_file() { + tracing::debug!(path=%path.display(), "loading config from file"); + settings = add_file_source(settings, path)?; } else { - bail!("Invalid path: {}", path.display()); + bail!( + "Invalid Rivet config path: {}. Ensure the path exists and is either a directory with config files or a specific config file.", + path.display() + ); } Ok(settings) diff --git a/engine/packages/engine/Cargo.toml b/engine/packages/engine/Cargo.toml index 0463022ad3..f1b6d7ebb9 100644 --- a/engine/packages/engine/Cargo.toml +++ b/engine/packages/engine/Cargo.toml @@ -32,6 +32,7 @@ rivet-guard.workspace = true rivet-logs.workspace = true rivet-pools.workspace = true rivet-runtime.workspace = true +rivet-serverless-backfill.workspace = true rivet-service-manager.workspace = true rivet-telemetry.workspace = true rivet-term.workspace = true diff --git a/engine/packages/engine/src/commands/wf/mod.rs b/engine/packages/engine/src/commands/wf/mod.rs index e45ab83a22..0fbcf511c5 100644 --- a/engine/packages/engine/src/commands/wf/mod.rs +++ b/engine/packages/engine/src/commands/wf/mod.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use anyhow::*; +use anyhow::{Result, ensure}; use clap::{Parser, ValueEnum}; use gas::db::{ self, Database, @@ -32,6 +32,18 @@ pub enum SubCommand { Silence { workflow_ids: Vec }, /// Sets the wake immediate property of a workflow to true. Wake { workflow_ids: Vec }, + /// Wakes dead workflows that match the name and error queries. + Revive { + #[clap(short = 'n', long)] + name: Vec, + /// Matches via substring (i.e. error = "database" will match workflows that died with error "database transaction failed"). + #[clap(short = 'e', long)] + error: Vec, + #[clap(short = 'd', long)] + dry_run: bool, + #[clap(short = 'p', long)] + parallelization: Option, + }, /// Lists the entire event history of a workflow. History { #[clap(index = 1)] @@ -58,7 +70,7 @@ pub enum SubCommand { impl SubCommand { pub async fn execute(self, config: rivet_config::Config) -> Result<()> { let pools = rivet_pools::Pools::new(config.clone()).await?; - let db = db::DatabaseKv::from_pools(pools).await? as Arc; + let db = db::DatabaseKv::new(config.clone(), pools).await? as Arc; match self { Self::Get { workflow_ids } => { @@ -85,6 +97,31 @@ impl SubCommand { } Self::Silence { workflow_ids } => db.silence_workflows(workflow_ids).await, Self::Wake { workflow_ids } => db.wake_workflows(workflow_ids).await, + Self::Revive { + name, + error, + dry_run, + parallelization, + } => { + ensure!(!name.is_empty(), "must provide at least one name"); + + let total = db + .revive_workflows( + &name.iter().map(|x| x.as_str()).collect::>(), + &error.iter().map(|x| x.as_str()).collect::>(), + dry_run, + parallelization.unwrap_or(1), + ) + .await?; + + if dry_run { + rivet_term::status::success("Workflows Matched", total); + } else { + rivet_term::status::success("Workflows Revived", total); + } + + Ok(()) + } Self::History { workflow_id, exclude_json, diff --git a/engine/packages/engine/src/run_config.rs b/engine/packages/engine/src/run_config.rs index 635575993d..a17033164a 100644 --- a/engine/packages/engine/src/run_config.rs +++ b/engine/packages/engine/src/run_config.rs @@ -40,6 +40,12 @@ pub fn config(_rivet_config: rivet_config::Config) -> Result { |config, pools| Box::pin(rivet_cache_purge::start(config, pools)), false, ), + Service::new( + "serverless_backfill", + ServiceKind::Oneshot, + |config, pools| Box::pin(rivet_serverless_backfill::start(config, pools)), + false, + ), ]; Ok(RunConfigData { services }) diff --git a/engine/packages/epoxy/src/ops/propose.rs b/engine/packages/epoxy/src/ops/propose.rs index 81ced977ae..73aff0881d 100644 --- a/engine/packages/epoxy/src/ops/propose.rs +++ b/engine/packages/epoxy/src/ops/propose.rs @@ -38,7 +38,8 @@ pub async fn epoxy_propose(ctx: &OperationCtx, input: &Input) -> Result Result Result { @@ -105,7 +108,8 @@ pub async fn run_paxos_accept( async move { replica::messages::accepted(&*tx, replica_id, payload).await } }) .custom_instrument(tracing::info_span!("accept_tx")) - .await?; + .await + .context("failed accepting")?; // EPaxos Step 17 let quorum = send_accepts( @@ -150,7 +154,8 @@ pub async fn commit( } }) .custom_instrument(tracing::info_span!("committed_tx")) - .await? + .await + .context("failed committing")? }; // EPaxos Step 23 diff --git a/engine/packages/epoxy/src/workflows/purger.rs b/engine/packages/epoxy/src/workflows/purger.rs index f68339b349..2ca69a004f 100644 --- a/engine/packages/epoxy/src/workflows/purger.rs +++ b/engine/packages/epoxy/src/workflows/purger.rs @@ -22,11 +22,11 @@ pub async fn epoxy_purger(ctx: &mut WorkflowCtx, input: &Input) -> Result<()> { let replica_id = input.replica_id; async move { - let sig = ctx.listen::().await?; + let signals = ctx.listen_n::(1024).await?; ctx.activity(PurgeInput { replica_id, - keys: sig.keys, + keys: signals.into_iter().flat_map(|sig| sig.keys).collect(), }) .await?; diff --git a/engine/packages/gasoline/src/ctx/test.rs b/engine/packages/gasoline/src/ctx/test.rs index 1479fe193a..dd2d12ea0a 100644 --- a/engine/packages/gasoline/src/ctx/test.rs +++ b/engine/packages/gasoline/src/ctx/test.rs @@ -55,8 +55,9 @@ impl TestCtx { let cache = rivet_cache::CacheInner::from_env(&config, pools.clone()) .expect("failed to create cache"); - let db = db::DatabaseKv::from_pools(pools.clone()).await?; - let debug_db = db::DatabaseKv::from_pools(pools.clone()).await? as Arc; + let db = db::DatabaseKv::new(config.clone(), pools.clone()).await?; + let debug_db = + db::DatabaseKv::new(config.clone(), pools.clone()).await? as Arc; let service_name = format!("{}-test--{}", rivet_env::service_name(), "gasoline_test"); let ray_id = Id::new_v1(config.dc_label()); diff --git a/engine/packages/gasoline/src/ctx/workflow.rs b/engine/packages/gasoline/src/ctx/workflow.rs index f5af928f6a..2078ac30bc 100644 --- a/engine/packages/gasoline/src/ctx/workflow.rs +++ b/engine/packages/gasoline/src/ctx/workflow.rs @@ -301,7 +301,7 @@ impl WorkflowCtx { let res = tokio::time::timeout(A::TIMEOUT, A::run(&ctx, input).in_current_span()) .await - .map_err(|_| WorkflowError::ActivityTimeout(0)); + .map_err(|_| WorkflowError::ActivityTimeout(A::NAME, 0)); let dt = start_instant.elapsed().as_secs_f64(); @@ -401,7 +401,7 @@ impl WorkflowCtx { ], ); - Err(WorkflowError::ActivityFailure(err, 0)) + Err(WorkflowError::ActivityFailure(A::NAME, err, 0)) } Err(err) => { tracing::debug!("activity timeout"); @@ -604,25 +604,28 @@ impl WorkflowCtx { // Convert error in the case of max retries exceeded. This will only act on retryable // errors let err = match err { - WorkflowError::ActivityFailure(err, _) => { + WorkflowError::ActivityFailure(name, err, _) => { if error_count.saturating_add(1) >= I::Activity::MAX_RETRIES { - WorkflowError::ActivityMaxFailuresReached(err) + WorkflowError::ActivityMaxFailuresReached(name, err) } else { // Add error count to the error for backoff calculation - WorkflowError::ActivityFailure(err, error_count) + WorkflowError::ActivityFailure(name, err, error_count) } } - WorkflowError::ActivityTimeout(_) => { + WorkflowError::ActivityTimeout(name, _) => { if error_count.saturating_add(1) >= I::Activity::MAX_RETRIES { - WorkflowError::ActivityMaxFailuresReached(err.into()) + WorkflowError::ActivityMaxFailuresReached(name, err.into()) } else { // Add error count to the error for backoff calculation - WorkflowError::ActivityTimeout(error_count) + WorkflowError::ActivityTimeout(name, error_count) } } WorkflowError::OperationTimeout(op_name, _) => { if error_count.saturating_add(1) >= I::Activity::MAX_RETRIES { - WorkflowError::ActivityMaxFailuresReached(err.into()) + WorkflowError::ActivityMaxFailuresReached( + I::Activity::NAME, + err.into(), + ) } else { // Add error count to the error for backoff calculation WorkflowError::OperationTimeout(op_name, error_count) diff --git a/engine/packages/gasoline/src/db/debug.rs b/engine/packages/gasoline/src/db/debug.rs index 5b37dabe46..524f8101b5 100644 --- a/engine/packages/gasoline/src/db/debug.rs +++ b/engine/packages/gasoline/src/db/debug.rs @@ -1,4 +1,4 @@ -use anyhow::*; +use anyhow::Result; use rivet_util::Id; use super::Database; @@ -39,6 +39,14 @@ pub trait DatabaseDebug: Database { ) -> Result>; async fn silence_signals(&self, signal_ids: Vec) -> Result<()>; + + async fn revive_workflows( + &self, + names: &[&str], + error_like: &[&str], + dry_run: bool, + parallelization: u128, + ) -> Result; } #[derive(Debug)] diff --git a/engine/packages/gasoline/src/db/kv/debug.rs b/engine/packages/gasoline/src/db/kv/debug.rs index 28ae6793f5..b4b214cccf 100644 --- a/engine/packages/gasoline/src/db/kv/debug.rs +++ b/engine/packages/gasoline/src/db/kv/debug.rs @@ -2,10 +2,11 @@ use std::{ collections::HashMap, ops::Deref, result::Result::{Err, Ok}, + time::Duration, }; use anyhow::{Context, Result, ensure}; -use futures_util::{StreamExt, TryStreamExt}; +use futures_util::{StreamExt, TryStreamExt, stream::FuturesUnordered}; use rivet_util::Id; use tracing::Instrument; use universaldb::utils::{FormalChunkedKey, FormalKey, IsolationLevel::*, end_of_key_range}; @@ -15,6 +16,7 @@ use universaldb::{ tuple::{PackResult, TupleDepth, TupleUnpack}, value::Value, }; +use uuid::Uuid; use super::{DatabaseKv, keys, update_metric}; use crate::{ @@ -33,6 +35,8 @@ use crate::{ }, }; +const EARLY_TXN_TIMEOUT: Duration = Duration::from_secs(3); + impl DatabaseKv { #[tracing::instrument(skip_all)] async fn get_workflows_inner( @@ -688,7 +692,10 @@ impl DatabaseDebug for DatabaseKv { continue; } - ensure!(!has_output, "cannot wake a completed workflow"); + if has_output { + tracing::warn!("cannot wake a completed workflow"); + continue; + } tx.write( &keys::wake::WorkflowWakeConditionKey::new( @@ -1193,6 +1200,196 @@ impl DatabaseDebug for DatabaseKv { .await .map_err(Into::into) } + + #[tracing::instrument(skip_all)] + async fn revive_workflows( + &self, + names: &[&str], + error_like: &[&str], + dry_run: bool, + parallelization: u128, + ) -> Result { + ensure!(parallelization > 0); + ensure!(parallelization < 1024); + + let chunk_size = u128::MAX / parallelization; + let mut futs = FuturesUnordered::new(); + + for i in 0..parallelization { + let start = i * chunk_size; + futs.push(self.revive_workflows_inner( + names, + error_like, + dry_run, + start, + start + chunk_size, + )); + } + + let mut total = 0; + while let Some(res) = futs.next().await { + total += res?; + } + + tracing::info!(?total, "workflows revived"); + + Ok(total) + } +} + +impl DatabaseKv { + pub async fn revive_workflows_inner( + &self, + names: &[&str], + error_like: &[&str], + dry_run: bool, + start: u128, + end: u128, + ) -> Result { + let mut total = 0; + let mut current_workflow_id = Some(Id::v1(Uuid::from_u128(start), self.config.dc_label())); + let end_workflow_id = Id::v1(Uuid::from_u128(end), self.config.dc_label()); + + loop { + let (new_current_workflow_id, workflow_ids) = self.pools + .udb()? + .run(|tx| { + async move { + let mut workflow_ids = Vec::new(); + + let key_end = keys::workflow::DataSubspaceKey::new_with_workflow_id(end_workflow_id); + let end = self.subspace.subspace(&key_end).range().1; + + let start = if let Some(current_workflow_id) = current_workflow_id { + let key_start = keys::workflow::DataSubspaceKey::new_with_workflow_id(current_workflow_id); + let mut bytes = self.subspace.subspace(&key_start).range().0; + + if let Some(b) = bytes.last_mut() { + *b = 255; + } + + bytes + } else { + let entire_subspace_key = keys::workflow::DataSubspaceKey::new(); + + self.subspace.subspace(&entire_subspace_key).range().0 + }; + + let mut stream = tx.get_ranges_keyvalues( + RangeOption { + mode: StreamingMode::WantAll, + ..(start, end).into() + }, + Snapshot, + ); + + let mut workflows_processed = 0; + let mut current_workflow_id = None; + let mut name_matches = false; + let mut state_matches = true; + let mut error_matches = error_like.is_empty(); + + let fut = async { + while let Some(entry) = stream.try_next().await? { + let workflow_id = *self.subspace.unpack::(entry.key())?; + + if let Some(curr) = current_workflow_id { + if workflow_id != curr { + // Save if matches query + if name_matches && state_matches && error_matches { + workflow_ids.push(curr); + } + + workflows_processed += 1; + + // Reset state + name_matches = false; + state_matches = true; + error_matches = error_like.is_empty(); + } + } + + current_workflow_id = Some(workflow_id); + + if let Ok(name_key) = + self.subspace.unpack::(entry.key()) + { + let workflow_name = name_key.deserialize(entry.value())?; + + name_matches = names.iter().any(|name| &workflow_name == name); + } else if let Ok(_) = self + .subspace + .unpack::(entry.key()) + { + state_matches = false; + } else if let Ok(_) = self + .subspace + .unpack::(entry.key()) + { + state_matches = false; + } else if let Ok(_) = self + .subspace + .unpack::(entry.key()) + { + state_matches = false; + } else if let Ok(_) = self + .subspace + .unpack::(entry.key()) + { + state_matches = false; + } else if let Ok(error_key) = self + .subspace + .unpack::(entry.key()) + { + let error = error_key.deserialize(entry.value())?.to_lowercase(); + + error_matches = error_like.is_empty() || error_like.iter().any(|err| error.contains(err)); + } + } + + if let (Some(workflow_id), true) = ( + current_workflow_id, + name_matches && state_matches && error_matches, + ) { + workflow_ids.push(workflow_id); + current_workflow_id = None; + } + + if current_workflow_id.is_some() { + workflows_processed += 1; + } + + anyhow::Ok(()) + }; + + match tokio::time::timeout(EARLY_TXN_TIMEOUT, fut).await { + Ok(res) => res?, + Err(_) => tracing::debug!("timed out reading workflows"), + } + + tracing::info!(?workflows_processed, matching_workflows=?workflow_ids.len(), "batch processed workflows"); + + Ok((current_workflow_id, workflow_ids)) + } + }) + .instrument(tracing::info_span!("find_dead_workflows_tx")) + .await?; + + current_workflow_id = new_current_workflow_id; + total += workflow_ids.len(); + + if !dry_run { + self.wake_workflows(workflow_ids).await?; + } + + if current_workflow_id.is_none() { + tracing::info!("reached end of workflows"); + break; + } + } + + Ok(total) + } } // Parses Id in third position, ignores the rest diff --git a/engine/packages/gasoline/src/db/kv/keys/workflow.rs b/engine/packages/gasoline/src/db/kv/keys/workflow.rs index 494e26ff52..4be980b6a9 100644 --- a/engine/packages/gasoline/src/db/kv/keys/workflow.rs +++ b/engine/packages/gasoline/src/db/kv/keys/workflow.rs @@ -1106,11 +1106,19 @@ impl<'de> TupleUnpack<'de> for WorkerIdKey { } } -pub struct DataSubspaceKey {} +pub struct DataSubspaceKey { + workflow_id: Option, +} impl DataSubspaceKey { pub fn new() -> Self { - DataSubspaceKey {} + DataSubspaceKey { workflow_id: None } + } + + pub fn new_with_workflow_id(workflow_id: Id) -> Self { + DataSubspaceKey { + workflow_id: Some(workflow_id), + } } } @@ -1120,8 +1128,16 @@ impl TuplePack for DataSubspaceKey { w: &mut W, tuple_depth: TupleDepth, ) -> std::io::Result { + let mut offset = VersionstampOffset::None { size: 0 }; + let t = (WORKFLOW, DATA); - t.pack(w, tuple_depth) + offset += t.pack(w, tuple_depth)?; + + if let Some(workflow_id) = &self.workflow_id { + offset += workflow_id.pack(w, tuple_depth)?; + } + + Ok(offset) } } diff --git a/engine/packages/gasoline/src/db/kv/mod.rs b/engine/packages/gasoline/src/db/kv/mod.rs index 2c39aa9c62..2e35ead33f 100644 --- a/engine/packages/gasoline/src/db/kv/mod.rs +++ b/engine/packages/gasoline/src/db/kv/mod.rs @@ -5,7 +5,7 @@ use std::{ collections::{HashMap, HashSet}, hash::{DefaultHasher, Hash, Hasher}, sync::Arc, - time::Instant, + time::{Duration, Instant}, }; use anyhow::{Context, Result, ensure}; @@ -48,8 +48,10 @@ mod system; const WORKER_LOST_THRESHOLD_MS: i64 = rivet_util::duration::seconds(30); /// How long before overwriting an existing metrics lock. const METRICS_LOCK_TIMEOUT_MS: i64 = rivet_util::duration::seconds(30); +const EARLY_TXN_TIMEOUT: Duration = Duration::from_millis(2500); pub struct DatabaseKv { + config: rivet_config::Config, pools: rivet_pools::Pools, subspace: universaldb::utils::Subspace, system: Mutex, @@ -417,8 +419,12 @@ impl Database for DatabaseKv { std::time::Duration::from_secs(4) } - async fn from_pools(pools: rivet_pools::Pools) -> anyhow::Result> { + async fn new( + config: rivet_config::Config, + pools: rivet_pools::Pools, + ) -> anyhow::Result> { Ok(Arc::new(DatabaseKv { + config, pools, subspace: universaldb::utils::Subspace::new(&(RIVET, GASOLINE, KV)), system: Mutex::new(system::SystemInfo::new()), @@ -436,6 +442,7 @@ impl Database for DatabaseKv { .map_err(WorkflowError::PoolsGeneric)? .subscribe(&subjects::convert(subject)) .await + .context("failed to subscribe to bump sub") .map_err(|x| WorkflowError::CreateSubscription(x.into()))?; let stream = async_stream::stream! { @@ -463,6 +470,7 @@ impl Database for DatabaseKv { .map_err(WorkflowError::PoolsGeneric)? .run(|tx| { async move { + let start = Instant::now(); let now = rivet_util::timestamp::now(); let mut last_ping_cache: Vec<(Id, i64)> = Vec::new(); @@ -484,7 +492,16 @@ impl Database for DatabaseKv { Snapshot, ); - while let Some(lease_key_entry) = stream.try_next().await? { + loop { + if start.elapsed() > EARLY_TXN_TIMEOUT { + tracing::warn!("timed out processing expired leases"); + break; + } + + let Some(lease_key_entry) = stream.try_next().await? else { + break; + }; + let lease_key = self .subspace .unpack::(lease_key_entry.key())?; @@ -580,6 +597,7 @@ impl Database for DatabaseKv { }) .custom_instrument(tracing::info_span!("clear_expired_leases_tx")) .await + .context("failed to clear expired leases") .map_err(WorkflowError::Udb)?; if expired_workflow_count != 0 { @@ -630,6 +648,7 @@ impl Database for DatabaseKv { }) .custom_instrument(tracing::info_span!("acquire_lock_tx")) .await + .context("failed to acquire metrics lock") .map_err(WorkflowError::Udb)?; if acquired_lock { @@ -659,6 +678,7 @@ impl Database for DatabaseKv { }) .custom_instrument(tracing::info_span!("read_metrics_tx")) .await + .context("failed to read metrics") .map_err(WorkflowError::Udb)?; let mut total_workflow_counts: Vec<(String, usize)> = Vec::new(); @@ -675,9 +695,9 @@ impl Database for DatabaseKv { .iter_mut() .find(|(name, _)| name == &workflow_name) { - entry.1 += 1; + entry.1 += count; } else { - total_workflow_counts.push((workflow_name, 1)); + total_workflow_counts.push((workflow_name, count)); } } keys::metric::GaugeMetric::WorkflowSleeping(workflow_name) => { @@ -690,9 +710,9 @@ impl Database for DatabaseKv { .iter_mut() .find(|(name, _)| name == &workflow_name) { - entry.1 += 1; + entry.1 += count; } else { - total_workflow_counts.push((workflow_name, 1)); + total_workflow_counts.push((workflow_name, count)); } } keys::metric::GaugeMetric::WorkflowDead(workflow_name, error) => { @@ -708,9 +728,9 @@ impl Database for DatabaseKv { .iter_mut() .find(|(name, _)| name == &workflow_name) { - entry.1 += 1; + entry.1 += count; } else { - total_workflow_counts.push((workflow_name, 1)); + total_workflow_counts.push((workflow_name, count)); } } keys::metric::GaugeMetric::WorkflowComplete(workflow_name) => { @@ -718,9 +738,9 @@ impl Database for DatabaseKv { .iter_mut() .find(|(name, _)| name == &workflow_name) { - entry.1 += 1; + entry.1 += count; } else { - total_workflow_counts.push((workflow_name, 1)); + total_workflow_counts.push((workflow_name, count)); } } keys::metric::GaugeMetric::SignalPending(signal_name) => { @@ -749,6 +769,7 @@ impl Database for DatabaseKv { }) .custom_instrument(tracing::info_span!("clear_lock_tx")) .await + .context("failed to release metrics lock") .map_err(WorkflowError::Udb)?; } @@ -789,6 +810,7 @@ impl Database for DatabaseKv { }) .custom_instrument(tracing::info_span!("update_worker_ping_tx")) .await + .context("failed to update worker ping") .map_err(WorkflowError::Udb)?; Ok(()) @@ -814,6 +836,7 @@ impl Database for DatabaseKv { }) .custom_instrument(tracing::info_span!("mark_worker_inactive_tx")) .await + .context("failed to mark worker inactive") .map_err(WorkflowError::Udb)?; Ok(()) @@ -1010,9 +1033,26 @@ impl Database for DatabaseKv { // Determine load shedding ratio based on linear mapping on cpu usage. We will gradually // pull less workflows as the cpu usage increases - let cpu_usage = { self.system.lock().await.cpu_usage() }; + // | . . + // 100% | _____ . + // | .\ . + // % wfs | . \ . + // | . \. + // 5% | . \_____ + // |_____.___.______ + // 0 50% 80% + // avg cpu usage + let cpu_usage_ratio = { + self.system + .lock() + .await + .cpu_usage_ratio(self.config.runtime.worker_cpu_max) + }; let load_shed_ratio_x1000 = - calc_pull_ratio((cpu_usage.max(100.0) * 10.0) as u64, 500, 1000, 800, 100); + calc_pull_ratio((cpu_usage_ratio * 1000.0) as u64, 500, 1000, 800, 50); + + // Record load shedding ratio metric + metrics::LOAD_SHEDDING_RATIO.record(load_shed_ratio_x1000 as f64 / 1000.0, &[]); let active_worker_subspace_start = tx.pack( &keys::worker::ActiveWorkerIdxKey::subspace(active_workers_after), @@ -1039,32 +1079,52 @@ impl Database for DatabaseKv { Ok(key.worker_id) }) .try_collect::>(), - futures_util::stream::iter(owned_filter) - .map(|wf_name| { - let wake_subspace_start = end_of_key_range(&tx.pack( - &keys::wake::WorkflowWakeConditionKey::subspace_without_ts( - wf_name.clone(), - ), - )); - let wake_subspace_end = - tx.pack(&keys::wake::WorkflowWakeConditionKey::subspace( - wf_name, - pull_before, + async { + let start = Instant::now(); + let mut buffer = Vec::new(); + let mut stream = futures_util::stream::iter(owned_filter) + .map(|wf_name| { + let wake_subspace_start = end_of_key_range(&tx.pack( + &keys::wake::WorkflowWakeConditionKey::subspace_without_ts( + wf_name.clone(), + ), )); + let wake_subspace_end = + tx.pack(&keys::wake::WorkflowWakeConditionKey::subspace( + wf_name, + pull_before, + )); - tx.get_ranges_keyvalues( - universaldb::RangeOption { - mode: StreamingMode::WantAll, - ..(wake_subspace_start, wake_subspace_end).into() - }, - // This is Snapshot to reduce contention with any new wake conditions - // being inserted. Conflicts are handled by workflow leases. - Snapshot, - ) - }) - .flatten() - .map(|res| tx.unpack::(res?.key())) - .try_collect::>(), + tx.get_ranges_keyvalues( + universaldb::RangeOption { + mode: StreamingMode::WantAll, + ..(wake_subspace_start, wake_subspace_end).into() + }, + // This is Snapshot to reduce contention with any new wake conditions + // being inserted. Conflicts are handled by workflow leases. + Snapshot, + ) + }) + .flatten() + .map(|res| { + tx.unpack::(res?.key()) + }); + + loop { + if start.elapsed() > EARLY_TXN_TIMEOUT { + tracing::warn!("timed out pulling wake conditions"); + break; + } + + let Some(wake_key) = stream.try_next().await? else { + break; + }; + + buffer.push(wake_key); + } + + anyhow::Ok(buffer) + } )?; // Sort for consistency across all workers @@ -1089,67 +1149,77 @@ impl Database for DatabaseKv { let active_worker_count = active_worker_ids.len() as u64; // Collect name and deadline ts for each wf id - let mut dedup_workflows: Vec = Vec::new(); + let mut dedup_workflows = HashMap::::new(); for wake_key in &wake_keys { - if let Some(wf) = dedup_workflows - .iter_mut() - .find(|wf| wf.workflow_id == wake_key.workflow_id) - { - let key_wake_deadline_ts = wake_key.condition.deadline_ts(); + let Some(wf) = dedup_workflows.get_mut(&wake_key.workflow_id) else { + dedup_workflows.insert( + wake_key.workflow_id, + MinimalPulledWorkflow { + workflow_id: wake_key.workflow_id, + workflow_name: wake_key.workflow_name.clone(), + wake_deadline_ts: wake_key.condition.deadline_ts(), + earliest_wake_condition_ts: wake_key.ts, + }, + ); - // Update wake condition ts if earlier - if wake_key.ts < wf.earliest_wake_condition_ts { - wf.earliest_wake_condition_ts = wake_key.ts; - } - - // Update wake deadline ts if earlier - if wf.wake_deadline_ts.is_none() - || key_wake_deadline_ts < wf.wake_deadline_ts - { - wf.wake_deadline_ts = key_wake_deadline_ts; + // Hard limit of 10k deduped workflows, this gets further limited to 1000 at + // `assigned_workflows` + if dedup_workflows.len() >= 10000 { + break; } continue; + }; + + let key_wake_deadline_ts = wake_key.condition.deadline_ts(); + + // Update wake condition ts if earlier + if wake_key.ts < wf.earliest_wake_condition_ts { + wf.earliest_wake_condition_ts = wake_key.ts; } - dedup_workflows.push(MinimalPulledWorkflow { - workflow_id: wake_key.workflow_id, - workflow_name: wake_key.workflow_name.clone(), - wake_deadline_ts: wake_key.condition.deadline_ts(), - earliest_wake_condition_ts: wake_key.ts, - }); + // Update wake deadline ts if earlier + if wf.wake_deadline_ts.is_none() + || key_wake_deadline_ts < wf.wake_deadline_ts + { + wf.wake_deadline_ts = key_wake_deadline_ts; + } } // Filter workflows in a way that spreads all current pending workflows across all active // workers evenly - let assigned_workflows = dedup_workflows.into_iter().filter(|wf| { - let mut hasher = DefaultHasher::new(); - - // Earliest wake condition ts is consistent for hashing purposes because when it - // changes it means a worker has leased it - wf.earliest_wake_condition_ts.hash(&mut hasher); - let wf_hash = hasher.finish(); - - let pseudorandom_value_x1000 = { - // Add a little pizazz to the hash so its a different number than wf_hash but - // still consistent - 1234i32.hash(&mut hasher); - hasher.finish() % 1000 // 0-1000 - }; - - if pseudorandom_value_x1000 < load_shed_ratio_x1000 { - return false; - } + let assigned_workflows = dedup_workflows + .into_values() + .filter(|wf| { + let mut hasher = DefaultHasher::new(); + + // Earliest wake condition ts is consistent for hashing purposes because when it + // changes it means a worker has leased it + wf.earliest_wake_condition_ts.hash(&mut hasher); + let wf_hash = hasher.finish(); + + let pseudorandom_value_x1000 = { + // Add a little pizazz to the hash so its a different number than wf_hash but + // still consistent + 1234i32.hash(&mut hasher); + hasher.finish() % 1000 // 0-1000 + }; + + if pseudorandom_value_x1000 > load_shed_ratio_x1000 { + return false; + } - let wf_worker_idx = wf_hash % active_worker_count; + let wf_worker_idx = wf_hash % active_worker_count; - // Every worker pulls workflows that has to the current worker as well as the next - // worker for redundancy. this results in increased txn conflicts but less chance of - // orphaned workflows - let next_worker_idx = (current_worker_idx + 1) % active_worker_count; + // Every worker pulls workflows that has to the current worker as well as the next + // worker for redundancy. this results in increased txn conflicts but less chance of + // orphaned workflows + let next_worker_idx = (current_worker_idx + 1) % active_worker_count; - wf_worker_idx == current_worker_idx || wf_worker_idx == next_worker_idx - }); + wf_worker_idx == current_worker_idx || wf_worker_idx == next_worker_idx + }) + // Hard limit of 1000 workflows per pull + .take(1000); // Check leases let leased_workflows = futures_util::stream::iter(assigned_workflows) @@ -1252,6 +1322,7 @@ impl Database for DatabaseKv { }) .custom_instrument(tracing::info_span!("pull_workflows_tx")) .await + .context("failed to lease workflows") .map_err(WorkflowError::Udb)?; let worker_id_str = worker_id.to_string(); @@ -1285,8 +1356,10 @@ impl Database for DatabaseKv { let ray_id_key = keys::workflow::RayIdKey::new(wf.workflow_id); let input_key = keys::workflow::InputKey::new(wf.workflow_id); let state_key = keys::workflow::StateKey::new(wf.workflow_id); + let output_key = keys::workflow::OutputKey::new(wf.workflow_id); let input_subspace = self.subspace.subspace(&input_key); let state_subspace = self.subspace.subspace(&state_key); + let output_subspace = self.subspace.subspace(&output_key); let active_history_subspace = self.subspace.subspace( &keys::history::HistorySubspaceKey::new( wf.workflow_id, @@ -1299,36 +1372,39 @@ impl Database for DatabaseKv { ray_id_entry, input_chunks, state_chunks, + has_output, events, ) = tokio::try_join!( - async { - tx.get(&self.subspace.pack(&create_ts_key), Serializable) - .await - }, - async { - tx.get(&self.subspace.pack(&ray_id_key), Serializable).await - }, - async { - tx.get_ranges_keyvalues( - universaldb::RangeOption { - mode: StreamingMode::WantAll, - ..(&input_subspace).into() - }, - Serializable, - ) - .try_collect::>() - .await - }, + tx.get(&self.subspace.pack(&create_ts_key), Serializable), + tx.get(&self.subspace.pack(&ray_id_key), Serializable), + tx.get_ranges_keyvalues( + universaldb::RangeOption { + mode: StreamingMode::WantAll, + ..(&input_subspace).into() + }, + Serializable, + ) + .try_collect::>(), + tx.get_ranges_keyvalues( + universaldb::RangeOption { + mode: StreamingMode::WantAll, + ..(&state_subspace).into() + }, + Serializable, + ) + .try_collect::>(), async { tx.get_ranges_keyvalues( universaldb::RangeOption { - mode: StreamingMode::WantAll, - ..(&state_subspace).into() + mode: StreamingMode::Exact, + limit: Some(1), + ..(&output_subspace).into() }, Serializable, ) - .try_collect::>() + .try_next() .await + .map(|entry| entry.is_some()) }, async { let mut events_by_location: HashMap> = @@ -1523,6 +1599,19 @@ impl Database for DatabaseKv { } )?; + if has_output { + tracing::warn!(workflow_id=?wf.workflow_id, "workflow already completed, ignoring"); + + // Clear lease + let lease_key = keys::workflow::LeaseKey::new(wf.workflow_id); + tx.clear(&self.subspace.pack(&lease_key)); + let worker_id_key = + keys::workflow::WorkerIdKey::new(wf.workflow_id); + tx.clear(&self.subspace.pack(&worker_id_key)); + + return Ok(None); + } + let create_ts = create_ts_key .deserialize(&create_ts_entry.context("key should exist")?)?; let ray_id = ray_id_key @@ -1534,7 +1623,7 @@ impl Database for DatabaseKv { state_key.combine(state_chunks)? }; - Result::<_>::Ok(PulledWorkflowData { + anyhow::Ok(Some(PulledWorkflowData { workflow_id: wf.workflow_id, workflow_name: wf.workflow_name, create_ts, @@ -1543,11 +1632,12 @@ impl Database for DatabaseKv { state, wake_deadline_ts: wf.wake_deadline_ts, events, - }) + })) } }) // TODO: How to get rid of this buffer? .buffer_unordered(512) + .try_filter_map(|x| std::future::ready(Ok(x))) .try_collect::>() .instrument(tracing::trace_span!("map_to_partial_workflow")) .await @@ -1555,6 +1645,7 @@ impl Database for DatabaseKv { }) .custom_instrument(tracing::info_span!("pull_workflow_history_tx")) .await + .context("failed to pull workflow history") .map_err(WorkflowError::Udb)?; let dt2 = start_instant2.elapsed().as_secs_f64(); @@ -3112,13 +3203,16 @@ fn value_to_str(v: &serde_json::Value) -> WorkflowResult { } fn calc_pull_ratio(x: u64, ax: u64, ay: u64, bx: u64, by: u64) -> u64 { - // must have neg slope, inversely proportional + // Must have neg slope, inversely proportional assert!(ax < bx); assert!(ay > by); - let neg_dy = ay - by; + // Bound domain + let x = x.max(ax).min(bx); + let dx = bx - ax; - let neg_b = ay * neg_dy / dx; + let neg_dy = ay - by; + let b = ay + ax * neg_dy / dx; - return neg_b.saturating_sub(x * neg_dy / dx); + return b.saturating_sub(x * neg_dy / dx); } diff --git a/engine/packages/gasoline/src/db/kv/system.rs b/engine/packages/gasoline/src/db/kv/system.rs index fc09dee12c..ab325be1d9 100644 --- a/engine/packages/gasoline/src/db/kv/system.rs +++ b/engine/packages/gasoline/src/db/kv/system.rs @@ -1,33 +1,162 @@ -use std::time::Instant; +use std::fs; +use std::time::{Duration, Instant}; -use sysinfo::{CpuRefreshKind, MINIMUM_CPU_UPDATE_INTERVAL, RefreshKind, System}; +use sysinfo::{CpuRefreshKind, Pid, ProcessRefreshKind, ProcessesToUpdate, RefreshKind, System}; + +const CPU_UPDATE_INTERVAL: Duration = Duration::from_millis(150); pub struct SystemInfo { system: System, + pid: Pid, last_cpu_usage_read: Instant, + cgroup_cpu_max: Option, + last_cgroup_usage_usec: Option, + last_cgroup_cores: f32, } impl SystemInfo { pub fn new() -> Self { SystemInfo { system: System::new_with_specifics( - RefreshKind::nothing().with_cpu(CpuRefreshKind::nothing().with_cpu_usage()), + RefreshKind::nothing() + .with_cpu(CpuRefreshKind::nothing().with_cpu_usage()) + .with_processes(ProcessRefreshKind::nothing().with_cpu()), ), + pid: Pid::from_u32(std::process::id()), last_cpu_usage_read: Instant::now(), + cgroup_cpu_max: CgroupCpuMax::read(), + last_cgroup_usage_usec: CgroupCpuUsage::read(), + last_cgroup_cores: 0.0, + } + } + + /// Returns a float 0.0-1.0 of the avg cpu usage in the current container (if cgroups are configured) or + /// otherwise for the current process. + pub fn cpu_usage_ratio(&mut self, cpu_max: Option) -> f32 { + // 1 = 1 core + let cpu_max = if let Some(cpu_max) = cpu_max { + cpu_max as f32 / 1000.0 + } else { + if let Some(CgroupCpuMax { quota, period }) = self.cgroup_cpu_max { + if quota > 0 { + quota as f32 / period as f32 + } else { + // Negative quota means unlimited, use cpu count + self.system.cpus().len() as f32 + } + } else { + self.system.cpus().len() as f32 + } + }; + + let total = if let Some(last_usage_usec) = self.last_cgroup_usage_usec { + // Use cgroup cpu.stat for usage (cumulative counter) + if self.last_cpu_usage_read.elapsed() > CPU_UPDATE_INTERVAL { + if let Some(current_usage_usec) = CgroupCpuUsage::read() { + let elapsed_usec = self.last_cpu_usage_read.elapsed().as_micros() as u64; + let usage_delta_usec = current_usage_usec.saturating_sub(last_usage_usec); + + // Calculate cores used: (usage_delta / elapsed_time) + let cores_used = if elapsed_usec > 0 { + usage_delta_usec as f32 / elapsed_usec as f32 + } else { + 0.0 + }; + + self.last_cgroup_usage_usec = Some(current_usage_usec); + self.last_cpu_usage_read = Instant::now(); + self.last_cgroup_cores = cores_used; + + cores_used + } else { + // Failed to read cgroup, disable cgroup usage tracking + self.last_cgroup_usage_usec = None; + 0.0 + } + } else { + // Not time to update yet, return last calculated value + self.last_cgroup_cores + } + } else { + // Use per-process CPU metrics + if self.last_cpu_usage_read.elapsed() > CPU_UPDATE_INTERVAL { + self.system.refresh_processes_specifics( + ProcessesToUpdate::Some(&[self.pid]), + true, + ProcessRefreshKind::nothing().with_cpu(), + ); + self.last_cpu_usage_read = Instant::now(); + } + + // Get CPU usage for current process (returns percentage 0-100 per core) + self.system + .process(self.pid) + .map(|p| p.cpu_usage() / 100.0) + .unwrap_or(0.0) + }; + + crate::metrics::CPU_USAGE.record(total as f64, &[]); + + total / cpu_max + } +} + +struct CgroupCpuMax { + quota: i64, + period: u64, +} + +impl CgroupCpuMax { + fn read() -> Option { + // cgroups v2 + if let Ok(content) = fs::read_to_string("/sys/fs/cgroup/cpu.max") { + let parts = content.trim().split_whitespace().collect::>(); + if parts.len() == 2 { + return Some(CgroupCpuMax { + quota: parts[0].parse::().ok()?, + period: parts[1].parse::().ok()?, + }); + } } + + // cgroups v1 + let quota = fs::read_to_string("/sys/fs/cgroup/cpu/cpu.cfs_quota_us") + .ok()? + .trim() + .parse() + .ok()?; + let period = fs::read_to_string("/sys/fs/cgroup/cpu/cpu.cfs_period_us") + .ok()? + .trim() + .parse() + .ok()?; + + Some(CgroupCpuMax { quota, period }) } +} + +struct CgroupCpuUsage; + +impl CgroupCpuUsage { + /// Reads CPU usage from cgroup cpu.stat + /// Returns usage in microseconds + fn read() -> Option { + // cgroups v2 + if let Ok(content) = fs::read_to_string("/sys/fs/cgroup/cpu.stat") { + for line in content.lines() { + if let Some(usage) = line.strip_prefix("usage_usec ") { + return usage.trim().parse().ok(); + } + } + } - /// Returns a float 0.0-100.0 of the avg cpu usage over the entire system. - pub fn cpu_usage(&mut self) -> f32 { - if self.last_cpu_usage_read.elapsed() > MINIMUM_CPU_UPDATE_INTERVAL { - self.system.refresh_cpu_usage(); - self.last_cpu_usage_read = Instant::now(); + // cgroups v1 + if let Ok(content) = fs::read_to_string("/sys/fs/cgroup/cpuacct/cpuacct.usage") { + // cpuacct.usage is in nanoseconds, convert to microseconds + let usage_nsec: u64 = content.trim().parse().ok()?; + return Some(usage_nsec / 1000); } - self.system - .cpus() - .iter() - .fold(0.0, |s, cpu| s + cpu.cpu_usage()) - / self.system.cpus().len() as f32 + None } } diff --git a/engine/packages/gasoline/src/db/mod.rs b/engine/packages/gasoline/src/db/mod.rs index 1e175ea3af..0909ecfacd 100644 --- a/engine/packages/gasoline/src/db/mod.rs +++ b/engine/packages/gasoline/src/db/mod.rs @@ -24,7 +24,7 @@ pub type DatabaseHandle = Arc; #[async_trait::async_trait] pub trait Database: Send { /// Create a new DB instance. - async fn from_pools(pools: rivet_pools::Pools) -> Result> + async fn new(config: rivet_config::Config, pools: rivet_pools::Pools) -> Result> where Self: Sized; diff --git a/engine/packages/gasoline/src/error.rs b/engine/packages/gasoline/src/error.rs index c2e30c7095..69fa449b30 100644 --- a/engine/packages/gasoline/src/error.rs +++ b/engine/packages/gasoline/src/error.rs @@ -9,17 +9,17 @@ pub type WorkflowResult = Result; #[derive(thiserror::Error, Debug)] pub enum WorkflowError { - #[error("workflow failure: {0:?}")] - WorkflowFailure(#[source] anyhow::Error), + #[error("workflow {0} failed: {1:?}")] + WorkflowFailure(&'static str, #[source] anyhow::Error), // Includes error count - #[error("activity failure: {0:?}")] - ActivityFailure(#[source] anyhow::Error, usize), + #[error("activity {0} failed: {1:?}")] + ActivityFailure(&'static str, #[source] anyhow::Error, usize), - #[error("activity failure, max retries reached: {0:?}")] - ActivityMaxFailuresReached(#[source] anyhow::Error), + #[error("activity {0} failed, max retries reached: {1:?}")] + ActivityMaxFailuresReached(&'static str, #[source] anyhow::Error), - #[error("operation failure ({0}): {1:?}")] + #[error("operation {0} failed: {1:?}")] OperationFailure(&'static str, #[source] anyhow::Error), #[error("workflow missing from registry: {0}")] @@ -146,8 +146,8 @@ pub enum WorkflowError { Config(#[source] anyhow::Error), // Includes error count - #[error("activity timed out")] - ActivityTimeout(usize), + #[error("activity {0} timed out")] + ActivityTimeout(&'static str, usize), // Includes error count #[error("operation {0} timed out")] @@ -186,8 +186,8 @@ impl WorkflowError { /// Returns the next deadline for a workflow to be woken up again based on the error. pub(crate) fn deadline_ts(&self) -> Option { match self { - WorkflowError::ActivityFailure(_, error_count) - | WorkflowError::ActivityTimeout(error_count) + WorkflowError::ActivityFailure(_, _, error_count) + | WorkflowError::ActivityTimeout(_, error_count) | WorkflowError::OperationTimeout(_, error_count) => { // NOTE: Max retry is handled in `WorkflowCtx::activity` let mut backoff = rivet_util::backoff::Backoff::new_at( @@ -218,8 +218,8 @@ impl WorkflowError { /// Any error that the workflow can continue on with its execution from. pub(crate) fn is_recoverable(&self) -> bool { match self { - WorkflowError::ActivityFailure(_, _) - | WorkflowError::ActivityTimeout(_) + WorkflowError::ActivityFailure(_, _, _) + | WorkflowError::ActivityTimeout(_, _) | WorkflowError::OperationTimeout(_, _) | WorkflowError::NoSignalFound(_) | WorkflowError::NoSignalFoundAndSleep(_, _) @@ -233,8 +233,8 @@ impl WorkflowError { /// Any error that the workflow can try again on a fixed number of times. Only used for printing. pub(crate) fn is_retryable(&self) -> bool { match self { - WorkflowError::ActivityFailure(_, _) - | WorkflowError::ActivityTimeout(_) + WorkflowError::ActivityFailure(_, _, _) + | WorkflowError::ActivityTimeout(_, _) | WorkflowError::OperationTimeout(_, _) => true, _ => false, } diff --git a/engine/packages/gasoline/src/metrics.rs b/engine/packages/gasoline/src/metrics.rs index b4f05d8bb1..3015ddccc9 100644 --- a/engine/packages/gasoline/src/metrics.rs +++ b/engine/packages/gasoline/src/metrics.rs @@ -148,4 +148,14 @@ lazy_static::lazy_static! { pub static ref OPERATION_ERRORS: Counter = METER.u64_counter("rivet_gasoline_operation_errors") .with_description("All errors made by this operation.") .build(); + + // MARK: Load Shedding + pub static ref CPU_USAGE: Histogram = METER.f64_histogram("rivet_gasoline_cpu_usage") + .with_description("CPU usage (100 per core).") + .with_boundaries(vec![0.0, 0.1, 0.25, 0.5, 1.0, 1.5, 2.0, 4.0, 8.0, 16.0]) + .build(); + pub static ref LOAD_SHEDDING_RATIO: Histogram = METER.f64_histogram("rivet_gasoline_load_shedding_ratio") + .with_description("Load shedding ratio (0-1) based on CPU usage, determining the fraction of workflows to pull.") + .with_boundaries(vec![0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]) + .build(); } diff --git a/engine/packages/gasoline/src/registry.rs b/engine/packages/gasoline/src/registry.rs index fe9905e4c8..aeb65eb4b2 100644 --- a/engine/packages/gasoline/src/registry.rs +++ b/engine/packages/gasoline/src/registry.rs @@ -72,7 +72,9 @@ impl Registry { // Differentiate between WorkflowError and user error Err(err) => match err.downcast::() { Ok(inner_err) => return Err(inner_err), - Err(err) => return Err(WorkflowError::WorkflowFailure(err)), + Err(err) => { + return Err(WorkflowError::WorkflowFailure(W::NAME, err)); + } }, }; diff --git a/engine/packages/guard-core/src/proxy_service.rs b/engine/packages/guard-core/src/proxy_service.rs index 89f2c288cc..3b5081321e 100644 --- a/engine/packages/guard-core/src/proxy_service.rs +++ b/engine/packages/guard-core/src/proxy_service.rs @@ -17,7 +17,7 @@ use serde_json; use rivet_runner_protocol as protocol; use std::{ borrow::Cow, - collections::{HashMap as StdHashMap, HashSet}, + collections::HashMap as StdHashMap, net::SocketAddr, sync::Arc, time::{Duration, Instant}, @@ -350,7 +350,7 @@ pub struct ProxyState { route_cache: RouteCache, rate_limiters: Cache<(Id, std::net::IpAddr), Arc>>, in_flight_counters: Cache<(Id, std::net::IpAddr), Arc>>, - in_flight_requests: Arc>>, + in_flight_requests: Cache, port_type: PortType, clickhouse_inserter: Option, tasks: Arc, @@ -379,7 +379,7 @@ impl ProxyState { .max_capacity(10_000) .time_to_live(PROXY_STATE_CACHE_TTL) .build(), - in_flight_requests: Arc::new(Mutex::new(HashSet::new())), + in_flight_requests: Cache::builder().max_capacity(10_000_000).build(), port_type, clickhouse_inserter, tasks: TaskGroup::new(), @@ -660,22 +660,20 @@ impl ProxyState { } // Release request ID - let mut requests = self.in_flight_requests.lock().await; - requests.remove(&request_id); + self.in_flight_requests.invalidate(&request_id).await; } /// Generate a unique request ID that is not currently in flight async fn generate_unique_request_id(&self) -> Result { const MAX_TRIES: u32 = 100; - let mut requests = self.in_flight_requests.lock().await; for attempt in 0..MAX_TRIES { let request_id = protocol::util::generate_request_id(); // Check if this ID is already in use - if !requests.contains(&request_id) { + if self.in_flight_requests.get(&request_id).await.is_none() { // Insert the ID and return it - requests.insert(request_id); + self.in_flight_requests.insert(request_id, ()).await; return Ok(request_id); } @@ -2668,6 +2666,11 @@ fn err_to_close_frame(err: anyhow::Error, ray_id: Option) -> CloseFrame { _ => CloseCode::Error, }; + match code { + CloseCode::Normal => tracing::info!("websocket closed"), + _ => tracing::error!(?err, "websocket failed"), + } + let reason = if let Some(ray_id) = ray_id { format!("{}.{}#{}", rivet_err.group(), rivet_err.code(), ray_id) } else { diff --git a/engine/packages/guard/src/lib.rs b/engine/packages/guard/src/lib.rs index e72c7c6edd..f4acd5872a 100644 --- a/engine/packages/guard/src/lib.rs +++ b/engine/packages/guard/src/lib.rs @@ -12,7 +12,7 @@ pub mod tls; pub async fn start(config: rivet_config::Config, pools: rivet_pools::Pools) -> Result<()> { let cache = rivet_cache::CacheInner::from_env(&config, pools.clone())?; let ctx = StandaloneCtx::new( - db::DatabaseKv::from_pools(pools.clone()).await?, + db::DatabaseKv::new(config.clone(), pools.clone()).await?, config.clone(), pools, cache, diff --git a/engine/packages/pegboard-runner/src/ws_to_tunnel_task.rs b/engine/packages/pegboard-runner/src/ws_to_tunnel_task.rs index 3b2fcca0ea..2eef4ae81b 100644 --- a/engine/packages/pegboard-runner/src/ws_to_tunnel_task.rs +++ b/engine/packages/pegboard-runner/src/ws_to_tunnel_task.rs @@ -142,6 +142,8 @@ async fn handle_message_mk2( } }; + tracing::debug!(?msg, "received message from runner"); + match msg { protocol::mk2::ToServer::ToServerPong(pong) => { let now = util::timestamp::now(); diff --git a/engine/packages/pegboard/src/lib.rs b/engine/packages/pegboard/src/lib.rs index 59e107384a..ca9c09182b 100644 --- a/engine/packages/pegboard/src/lib.rs +++ b/engine/packages/pegboard/src/lib.rs @@ -16,8 +16,8 @@ pub fn registry() -> WorkflowResult { registry.register_workflow::()?; registry.register_workflow::()?; registry.register_workflow::()?; - registry.register_workflow::()?; registry.register_workflow::()?; + registry.register_workflow::()?; Ok(registry) } diff --git a/engine/packages/pegboard/src/ops/runner_config/delete.rs b/engine/packages/pegboard/src/ops/runner_config/delete.rs index 7c19fc0ca4..b3d3c96b64 100644 --- a/engine/packages/pegboard/src/ops/runner_config/delete.rs +++ b/engine/packages/pegboard/src/ops/runner_config/delete.rs @@ -44,12 +44,18 @@ pub async fn pegboard_runner_config_delete(ctx: &OperationCtx, input: &Input) -> // Bump pool when a serverless config is modified if delete_pool { - ctx.signal(crate::workflows::runner_pool::Bump {}) + let res = ctx + .signal(crate::workflows::runner_pool::Bump {}) .to_workflow::() .tag("namespace_id", input.namespace_id) .tag("runner_name", input.name.clone()) + .graceful_not_found() .send() .await?; + + if res.is_none() { + tracing::debug!(namespace_id=?input.namespace_id, name=%input.name, "no runner pool workflow to bump"); + } } Ok(()) diff --git a/engine/packages/pegboard/src/ops/runner_config/upsert.rs b/engine/packages/pegboard/src/ops/runner_config/upsert.rs index 9c4f00ad72..09da081335 100644 --- a/engine/packages/pegboard/src/ops/runner_config/upsert.rs +++ b/engine/packages/pegboard/src/ops/runner_config/upsert.rs @@ -169,12 +169,27 @@ pub async fn pegboard_runner_config_upsert(ctx: &OperationCtx, input: &Input) -> .dispatch() .await?; } else if input.config.affects_pool() { - ctx.signal(crate::workflows::runner_pool::Bump {}) + let res = ctx + .signal(crate::workflows::runner_pool::Bump {}) .to_workflow::() .tag("namespace_id", input.namespace_id) .tag("runner_name", input.name.clone()) + .graceful_not_found() .send() .await?; + + // Backfill + if res.is_none() { + ctx.workflow(crate::workflows::runner_pool::Input { + namespace_id: input.namespace_id, + runner_name: input.name.clone(), + }) + .tag("namespace_id", input.namespace_id) + .tag("runner_name", input.name.clone()) + .unique() + .dispatch() + .await?; + } } Ok(res.endpoint_config_changed) diff --git a/engine/packages/pegboard/src/workflows/actor/destroy.rs b/engine/packages/pegboard/src/workflows/actor/destroy.rs index d73493ed20..8c78ca62a3 100644 --- a/engine/packages/pegboard/src/workflows/actor/destroy.rs +++ b/engine/packages/pegboard/src/workflows/actor/destroy.rs @@ -98,7 +98,6 @@ async fn update_state_and_db( let runner_id = state.runner_id.clone(); let namespace_id = state.namespace_id.clone(); let runner_name_selector = state.runner_name_selector.clone(); - let for_serverless = state.for_serverless.clone(); let allocated_serverless_slot = state.allocated_serverless_slot.clone(); let name = state.name.clone(); let create_ts = state.create_ts.clone(); @@ -109,28 +108,15 @@ async fn update_state_and_db( tx.write(&keys::actor::DestroyTsKey::new(input.actor_id), destroy_ts)?; - if let Some(runner_id) = runner_id { - clear_slot( - input.actor_id, - namespace_id, - &runner_name_selector, - runner_id, - for_serverless, - &tx, - ) - .await?; - } else if allocated_serverless_slot { - // Clear the serverless slot even if we do not have a runner id. This happens when the - // actor is destroyed while pending allocation - tx.atomic_op( - &rivet_types::keys::pegboard::ns::ServerlessDesiredSlotsKey::new( - namespace_id, - runner_name_selector.clone(), - ), - &(-1i64).to_le_bytes(), - MutationType::Add, - ); - } + clear_slot( + input.actor_id, + namespace_id, + &runner_name_selector, + runner_id, + allocated_serverless_slot, + &tx, + ) + .await?; // Update namespace indexes tx.delete(&keys::ns::ActiveActorKey::new( @@ -209,84 +195,89 @@ pub(crate) async fn clear_slot( actor_id: Id, namespace_id: Id, runner_name_selector: &str, - runner_id: Id, - for_serverless: bool, + runner_id: Option, + allocated_serverless_slot: bool, tx: &universaldb::Transaction, ) -> Result<()> { let tx = tx.with_subspace(keys::subspace()); - tx.delete(&keys::actor::RunnerIdKey::new(actor_id)); - - // This is cleared when the state changes as well as when the actor is destroyed to ensure - // consistency during rescheduling and forced deletion. - tx.delete(&keys::runner::ActorKey::new(runner_id, actor_id)); - - let runner_workflow_id_key = keys::runner::WorkflowIdKey::new(runner_id); - let runner_version_key = keys::runner::VersionKey::new(runner_id); - let runner_remaining_slots_key = keys::runner::RemainingSlotsKey::new(runner_id); - let runner_total_slots_key = keys::runner::TotalSlotsKey::new(runner_id); - let runner_last_ping_ts_key = keys::runner::LastPingTsKey::new(runner_id); - let runner_protocol_version_key = keys::runner::ProtocolVersionKey::new(runner_id); - - let ( - runner_workflow_id, - runner_version, - runner_remaining_slots, - runner_total_slots, - runner_last_ping_ts, - runner_protocol_version, - ) = tokio::try_join!( - tx.read(&runner_workflow_id_key, Serializable), - tx.read(&runner_version_key, Serializable), - tx.read(&runner_remaining_slots_key, Serializable), - tx.read(&runner_total_slots_key, Serializable), - tx.read(&runner_last_ping_ts_key, Serializable), - tx.read_opt(&runner_protocol_version_key, Serializable), - )?; - - let old_runner_remaining_millislots = (runner_remaining_slots * 1000) / runner_total_slots; - let new_runner_remaining_slots = runner_remaining_slots + 1; - - // Write new remaining slots - tx.write(&runner_remaining_slots_key, new_runner_remaining_slots)?; - - let old_runner_alloc_key = keys::ns::RunnerAllocIdxKey::new( - namespace_id, - runner_name_selector.to_string(), - runner_version, - old_runner_remaining_millislots, - runner_last_ping_ts, - runner_id, - ); - - // Only update allocation idx if it existed before - if tx.exists(&old_runner_alloc_key, Serializable).await? { - // Clear old key - tx.delete(&old_runner_alloc_key); - - let new_remaining_millislots = (new_runner_remaining_slots * 1000) / runner_total_slots; - let new_runner_alloc_key = keys::ns::RunnerAllocIdxKey::new( + // Only clear slot if we have a runner id + if let Some(runner_id) = runner_id { + tx.delete(&keys::actor::RunnerIdKey::new(actor_id)); + + // This is cleared when the state changes as well as when the actor is destroyed to ensure + // consistency during rescheduling and forced deletion. + tx.delete(&keys::runner::ActorKey::new(runner_id, actor_id)); + + let runner_workflow_id_key = keys::runner::WorkflowIdKey::new(runner_id); + let runner_version_key = keys::runner::VersionKey::new(runner_id); + let runner_remaining_slots_key = keys::runner::RemainingSlotsKey::new(runner_id); + let runner_total_slots_key = keys::runner::TotalSlotsKey::new(runner_id); + let runner_last_ping_ts_key = keys::runner::LastPingTsKey::new(runner_id); + let runner_protocol_version_key = keys::runner::ProtocolVersionKey::new(runner_id); + + let ( + runner_workflow_id, + runner_version, + runner_remaining_slots, + runner_total_slots, + runner_last_ping_ts, + runner_protocol_version, + ) = tokio::try_join!( + tx.read(&runner_workflow_id_key, Serializable), + tx.read(&runner_version_key, Serializable), + tx.read(&runner_remaining_slots_key, Serializable), + tx.read(&runner_total_slots_key, Serializable), + tx.read(&runner_last_ping_ts_key, Serializable), + tx.read_opt(&runner_protocol_version_key, Serializable), + )?; + + let old_runner_remaining_millislots = (runner_remaining_slots * 1000) / runner_total_slots; + let new_runner_remaining_slots = runner_remaining_slots + 1; + + // Write new remaining slots + tx.write(&runner_remaining_slots_key, new_runner_remaining_slots)?; + + let old_runner_alloc_key = keys::ns::RunnerAllocIdxKey::new( namespace_id, runner_name_selector.to_string(), runner_version, - new_remaining_millislots, + old_runner_remaining_millislots, runner_last_ping_ts, runner_id, ); - tx.write( - &new_runner_alloc_key, - rivet_data::converted::RunnerAllocIdxKeyData { - workflow_id: runner_workflow_id, - remaining_slots: new_runner_remaining_slots, - total_slots: runner_total_slots, - // We default here because its not important for mk1 protocol runners - protocol_version: runner_protocol_version.unwrap_or(PROTOCOL_MK1_VERSION), - }, - )?; + // Only update allocation idx if it existed before + if tx.exists(&old_runner_alloc_key, Serializable).await? { + // Clear old key + tx.delete(&old_runner_alloc_key); + + let new_remaining_millislots = (new_runner_remaining_slots * 1000) / runner_total_slots; + let new_runner_alloc_key = keys::ns::RunnerAllocIdxKey::new( + namespace_id, + runner_name_selector.to_string(), + runner_version, + new_remaining_millislots, + runner_last_ping_ts, + runner_id, + ); + + tx.write( + &new_runner_alloc_key, + rivet_data::converted::RunnerAllocIdxKeyData { + workflow_id: runner_workflow_id, + remaining_slots: new_runner_remaining_slots, + total_slots: runner_total_slots, + // We default here because its not important for mk1 protocol runners + protocol_version: runner_protocol_version.unwrap_or(PROTOCOL_MK1_VERSION), + }, + )?; + } } - if for_serverless { + if allocated_serverless_slot { + // Clear the serverless slot even if we do not have a runner id. This happens when the + // actor is destroyed while pending allocation tx.atomic_op( &rivet_types::keys::pegboard::ns::ServerlessDesiredSlotsKey::new( namespace_id, diff --git a/engine/packages/pegboard/src/workflows/actor/runtime.rs b/engine/packages/pegboard/src/workflows/actor/runtime.rs index 268d3fcde8..a9e42f54ec 100644 --- a/engine/packages/pegboard/src/workflows/actor/runtime.rs +++ b/engine/packages/pegboard/src/workflows/actor/runtime.rs @@ -535,7 +535,7 @@ pub async fn deallocate(ctx: &ActivityCtx, input: &DeallocateInput) -> Result Result Result { + let mut state = ctx.state::()?; + + let allocated_serverless_slot = state.allocated_serverless_slot; + // Clear self from alloc queue let cleared = ctx .udb()? @@ -1050,13 +1051,28 @@ pub async fn clear_pending_allocation( let exists = tx.get(&pending_alloc_key, Serializable).await?.is_some(); - tx.clear(&pending_alloc_key); + if exists { + tx.clear(&pending_alloc_key); + + if allocated_serverless_slot { + tx.atomic_op( + &rivet_types::keys::pegboard::ns::ServerlessDesiredSlotsKey::new( + input.namespace_id, + input.runner_name_selector.clone(), + ), + &(-1i64).to_le_bytes(), + MutationType::Add, + ); + } + } Ok(exists) }) .custom_instrument(tracing::info_span!("actor_clear_pending_alloc_tx")) .await?; + state.allocated_serverless_slot = false; + Ok(cleared) } @@ -1187,6 +1203,8 @@ pub async fn check_runners( ) -> Result { ctx.udb()? .run(|tx| async move { + let tx = tx.with_subspace(keys::subspace()); + // Diff the list of seen runners with the list of active runners so we know which we can clean up // state let remove_runners = futures_util::stream::iter(input.seen_runners.clone()) diff --git a/engine/packages/pegboard/src/workflows/runner.rs b/engine/packages/pegboard/src/workflows/runner.rs index 4d06a657b5..ce4fc2be6b 100644 --- a/engine/packages/pegboard/src/workflows/runner.rs +++ b/engine/packages/pegboard/src/workflows/runner.rs @@ -1,3 +1,5 @@ +use std::time::{Duration, Instant}; + use futures_util::{FutureExt, StreamExt, TryStreamExt}; use gas::prelude::*; use rivet_data::converted::{ActorNameKeyData, MetadataKeyData, RunnerByKeyKeyData}; @@ -14,6 +16,7 @@ use crate::{keys, metrics, workflows::actor::Allocate}; /// Batch size of how many events to ack. const EVENT_ACK_BATCH_SIZE: i64 = 500; +const EARLY_TXN_TIMEOUT: Duration = Duration::from_millis(2500); #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Input { @@ -965,6 +968,7 @@ pub(crate) async fn allocate_pending_actors( let (allocations, pending_actor_count) = ctx .udb()? .run(|tx| async move { + let start = Instant::now(); let tx = tx.with_subspace(keys::subspace()); let mut allocations = Vec::new(); @@ -987,6 +991,11 @@ pub(crate) async fn allocate_pending_actors( let ping_threshold_ts = util::timestamp::now() - runner_eligible_threshold; 'queue_loop: loop { + if start.elapsed() > EARLY_TXN_TIMEOUT { + tracing::warn!("timed out processing pending actors queue"); + break; + } + let Some(queue_entry) = queue_stream.try_next().await? else { break; }; @@ -1013,6 +1022,11 @@ pub(crate) async fn allocate_pending_actors( let mut highest_version = None; loop { + if start.elapsed() > EARLY_TXN_TIMEOUT { + tracing::warn!("timed out processing pending actors queue"); + break 'queue_loop; + } + let Some(entry) = stream.try_next().await? else { break; }; diff --git a/engine/packages/pegboard/src/workflows/runner2.rs b/engine/packages/pegboard/src/workflows/runner2.rs index fe39291caa..174bfd66f9 100644 --- a/engine/packages/pegboard/src/workflows/runner2.rs +++ b/engine/packages/pegboard/src/workflows/runner2.rs @@ -1,3 +1,5 @@ +use std::time::{Duration, Instant}; + use futures_util::{FutureExt, StreamExt, TryStreamExt}; use gas::prelude::*; use rivet_data::converted::RunnerByKeyKeyData; @@ -12,6 +14,8 @@ use vbare::OwnedVersionedData; use crate::{keys, metrics, workflows::actor::Allocate}; +const EARLY_TXN_TIMEOUT: Duration = Duration::from_millis(2500); + #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Input { pub runner_id: Id, @@ -611,6 +615,7 @@ pub(crate) async fn allocate_pending_actors( let (allocations, pending_actor_count) = ctx .udb()? .run(|tx| async move { + let start = Instant::now(); let tx = tx.with_subspace(keys::subspace()); let mut allocations = Vec::new(); @@ -633,6 +638,11 @@ pub(crate) async fn allocate_pending_actors( let ping_threshold_ts = util::timestamp::now() - runner_eligible_threshold; 'queue_loop: loop { + if start.elapsed() > EARLY_TXN_TIMEOUT { + tracing::warn!("timed out processing pending actors queue"); + break; + } + let Some(queue_entry) = queue_stream.try_next().await? else { break; }; @@ -659,6 +669,11 @@ pub(crate) async fn allocate_pending_actors( let mut highest_version = None; loop { + if start.elapsed() > EARLY_TXN_TIMEOUT { + tracing::warn!("timed out processing pending actors queue"); + break 'queue_loop; + } + let Some(entry) = stream.try_next().await? else { break; }; diff --git a/engine/packages/pegboard/src/workflows/runner_pool.rs b/engine/packages/pegboard/src/workflows/runner_pool.rs index d1bb91120c..916458a0c1 100644 --- a/engine/packages/pegboard/src/workflows/runner_pool.rs +++ b/engine/packages/pegboard/src/workflows/runner_pool.rs @@ -38,6 +38,14 @@ pub async fn pegboard_runner_pool(ctx: &mut WorkflowCtx, input: &Input) -> Resul }) .await? else { + // Drain all + for runner in &state.runners { + ctx.signal(serverless::receiver::Drain {}) + .to_workflow_id(runner.receiver_wf_id) + .send() + .await?; + } + return Ok(Loop::Break(())); }; @@ -96,12 +104,22 @@ pub async fn pegboard_runner_pool(ctx: &mut WorkflowCtx, input: &Input) -> Resul } // Wait for Bump or serverless signals until we tick again - for sig in ctx.listen_n::
(1024).await? { + for sig in ctx.listen_n::
(512).await? { match sig { Main::OutboundConnDrainStarted(sig) => { - state - .runners - .retain(|r| r.receiver_wf_id != sig.receiver_wf_id); + let (new, drain_started) = + std::mem::take(&mut state.runners) + .into_iter() + .partition::, _>(|r| r.receiver_wf_id != sig.receiver_wf_id); + state.runners = new; + + for runner in drain_started { + // TODO: Spawn sub wf to process these so this is not blocking the loop + ctx.signal(serverless::receiver::Drain {}) + .to_workflow_id(runner.receiver_wf_id) + .send() + .await?; + } } Main::Bump(_) => {} } @@ -190,6 +208,12 @@ async fn read_desired(ctx: &ActivityCtx, input: &ReadDesiredInput) -> Result Result {} + Ok(OutboundReqOutput::Continue) => { + if let Err(err) = ctx + .signal(runner_pool::Bump {}) + // This is ok because bumps are not stateful + .bypass_signal_from_workflow_I_KNOW_WHAT_IM_DOING() + .to_workflow_id(input.pool_wf_id) + .send() + .await + { + tracing::debug!(?err, "failed to send bump signal"); + + return Ok(OutboundReqOutput::Draining { drain_sent: false }); + } + } Ok(OutboundReqOutput::Draining { drain_sent }) => { return Ok(OutboundReqOutput::Draining { drain_sent }); } @@ -279,8 +292,6 @@ async fn outbound_req_inner( match event { Ok(sse::Event::Open) => {} Ok(sse::Event::Message(msg)) => { - tracing::debug!(%msg.data, "received outbound req message"); - if runner_id.is_none() { let data = BASE64.decode(msg.data).context("invalid base64 message")?; let payload = @@ -396,8 +407,6 @@ async fn finish_non_critical_draining( match event { Ok(sse::Event::Open) => {} Ok(sse::Event::Message(msg)) => { - tracing::debug!(%msg.data, ?runner_id, "received outbound req message"); - // If runner_id is none at this point it means we did not send the stopping signal yet, so // send it now if runner_id.is_none() { @@ -457,7 +466,7 @@ async fn drain_runner(ctx: &ActivityCtx, runner_id: Id) -> Result<()> { }) // This is ok, because runner_id changes every retry of outbound_req .bypass_signal_from_workflow_I_KNOW_WHAT_IM_DOING() - .to_workflow::() + .to_workflow::() .tag("runner_id", runner_id) .graceful_not_found() .send() diff --git a/engine/packages/serverless-backfill/Cargo.toml b/engine/packages/serverless-backfill/Cargo.toml new file mode 100644 index 0000000000..a1ad64bf2b --- /dev/null +++ b/engine/packages/serverless-backfill/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "rivet-serverless-backfill" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow.workspace = true +gas.workspace = true +pegboard.workspace = true +rivet-config.workspace = true +rivet-types.workspace = true +tracing.workspace = true +universaldb.workspace = true diff --git a/engine/packages/serverless-backfill/src/lib.rs b/engine/packages/serverless-backfill/src/lib.rs new file mode 100644 index 0000000000..f1149de4ae --- /dev/null +++ b/engine/packages/serverless-backfill/src/lib.rs @@ -0,0 +1,87 @@ +use anyhow::Result; +use futures_util::{StreamExt, TryStreamExt}; +use gas::prelude::*; +use universaldb::options::StreamingMode; +use universaldb::utils::IsolationLevel::*; + +#[tracing::instrument(skip_all)] +pub async fn start(config: rivet_config::Config, pools: rivet_pools::Pools) -> Result<()> { + let cache = rivet_cache::CacheInner::from_env(&config, pools.clone())?; + let ctx = StandaloneCtx::new( + db::DatabaseKv::new(config.clone(), pools.clone()).await?, + config.clone(), + pools, + cache, + "serverless_backfill", + Id::new_v1(config.dc_label()), + Id::new_v1(config.dc_label()), + )?; + + let serverless_data = ctx + .udb()? + .run(|tx| async move { + let tx = tx.with_subspace(pegboard::keys::subspace()); + + let serverless_desired_subspace = pegboard::keys::subspace().subspace( + &rivet_types::keys::pegboard::ns::ServerlessDesiredSlotsKey::entire_subspace(), + ); + + tx.get_ranges_keyvalues( + universaldb::RangeOption { + mode: StreamingMode::WantAll, + ..(&serverless_desired_subspace).into() + }, + // NOTE: This is a snapshot to prevent conflict with updates to this subspace + Snapshot, + ) + .map(|res| { + tx.unpack::(res?.key()) + }) + .try_collect::>() + .await + }) + .custom_instrument(tracing::info_span!("read_serverless_tx")) + .await?; + + if serverless_data.is_empty() { + return Ok(()); + } + + tracing::info!("backfilling serverless"); + + let runner_configs = ctx + .op(pegboard::ops::runner_config::get::Input { + runners: serverless_data + .iter() + .map(|key| (key.namespace_id, key.runner_name.clone())) + .collect(), + bypass_cache: true, + }) + .await?; + + for key in &serverless_data { + if !runner_configs + .iter() + .any(|rc| rc.namespace_id == key.namespace_id) + { + tracing::debug!( + namespace_id=?key.namespace_id, + runner_name=?key.runner_name, + "runner config not found, likely deleted" + ); + continue; + }; + + ctx.workflow(pegboard::workflows::runner_pool::Input { + namespace_id: key.namespace_id, + runner_name: key.runner_name.clone(), + }) + .tag("namespace_id", key.namespace_id) + .tag("runner_name", key.runner_name.clone()) + .unique() + .dispatch() + .await?; + } + + Ok(()) +} diff --git a/engine/packages/universaldb/src/driver/postgres/database.rs b/engine/packages/universaldb/src/driver/postgres/database.rs index 1ab832997d..6450a1e4a4 100644 --- a/engine/packages/universaldb/src/driver/postgres/database.rs +++ b/engine/packages/universaldb/src/driver/postgres/database.rs @@ -16,12 +16,12 @@ use crate::{ driver::{BoxFut, DatabaseDriver, Erased}, error::DatabaseError, options::DatabaseOption, + transaction::TXN_TIMEOUT, utils::{MaybeCommitted, calculate_tx_retry_backoff}, }; use super::transaction::PostgresTransactionDriver; -const TXN_TIMEOUT: Duration = Duration::from_secs(5); const GC_INTERVAL: Duration = Duration::from_secs(5); pub struct PostgresDatabaseDriver { diff --git a/engine/packages/universaldb/src/driver/rocksdb/database.rs b/engine/packages/universaldb/src/driver/rocksdb/database.rs index 23bac0ec5c..e2de487482 100644 --- a/engine/packages/universaldb/src/driver/rocksdb/database.rs +++ b/engine/packages/universaldb/src/driver/rocksdb/database.rs @@ -4,7 +4,6 @@ use std::{ Arc, atomic::{AtomicI32, Ordering}, }, - time::Duration, }; use anyhow::{Context, Result}; @@ -15,6 +14,7 @@ use crate::{ driver::{BoxFut, DatabaseDriver, Erased}, error::DatabaseError, options::DatabaseOption, + transaction::TXN_TIMEOUT, utils::{MaybeCommitted, calculate_tx_retry_backoff}, }; @@ -22,8 +22,6 @@ use super::{ transaction::RocksDbTransactionDriver, transaction_conflict_tracker::TransactionConflictTracker, }; -const TXN_TIMEOUT: Duration = Duration::from_secs(5); - pub struct RocksDbDatabaseDriver { db: Arc, max_retries: AtomicI32, diff --git a/engine/packages/universaldb/src/transaction.rs b/engine/packages/universaldb/src/transaction.rs index 96631e9514..0fe9f02060 100644 --- a/engine/packages/universaldb/src/transaction.rs +++ b/engine/packages/universaldb/src/transaction.rs @@ -1,4 +1,4 @@ -use std::{future::Future, ops::Deref, pin::Pin, sync::Arc}; +use std::{future::Future, ops::Deref, pin::Pin, sync::Arc, time::Duration}; use anyhow::{Context, Result}; @@ -15,6 +15,8 @@ use crate::{ value::{Slice, Value, Values}, }; +pub const TXN_TIMEOUT: Duration = Duration::from_secs(5); + #[derive(Clone)] pub struct Transaction { pub(crate) driver: Arc, diff --git a/engine/packages/universalpubsub/src/chunking.rs b/engine/packages/universalpubsub/src/chunking.rs index 2d276233fc..03be93cf0b 100644 --- a/engine/packages/universalpubsub/src/chunking.rs +++ b/engine/packages/universalpubsub/src/chunking.rs @@ -103,7 +103,7 @@ impl ChunkTracker { .retain(|_, buffer| now.duration_since(buffer.last_chunk_ts) < CHUNK_BUFFER_MAX_AGE); let size_after = self.chunks_in_process.len(); - tracing::debug!( + tracing::trace!( ?size_before, ?size_after, "performed chunk buffer garbage collection" diff --git a/engine/packages/workflow-worker/src/lib.rs b/engine/packages/workflow-worker/src/lib.rs index 59765159cc..5b5da02487 100644 --- a/engine/packages/workflow-worker/src/lib.rs +++ b/engine/packages/workflow-worker/src/lib.rs @@ -7,7 +7,7 @@ pub async fn start(config: rivet_config::Config, pools: rivet_pools::Pools) -> R .merge(namespace::registry()?)? .merge(epoxy::registry()?)?; - let db = db::DatabaseKv::from_pools(pools.clone()).await?; + let db = db::DatabaseKv::new(config.clone(), pools.clone()).await?; let worker = Worker::new(reg.handle(), db, config, pools); // Start worker diff --git a/k8s/engine/06-rivet-engine-singleton-deployment.yaml b/k8s/engine/06-rivet-engine-singleton-deployment.yaml deleted file mode 100644 index 6d8666b351..0000000000 --- a/k8s/engine/06-rivet-engine-singleton-deployment.yaml +++ /dev/null @@ -1,79 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - labels: - app: rivet-engine-singleton - component: singleton - service: engine - name: rivet-engine-singleton - namespace: rivet-engine -spec: - replicas: 1 - selector: - matchLabels: - app: rivet-engine-singleton - template: - metadata: - annotations: - checksum/config: REPLACE_WITH_CONFIG_CHECKSUM - cluster-autoscaler.kubernetes.io/safe-to-evict: "false" - labels: - app: rivet-engine-singleton - component: singleton - service: engine - spec: - containers: - - args: - - start - - --services - - singleton - - --services - - api-peer - env: - - name: RIVET_CONFIG_PATH - value: /etc/rivet/config.jsonc - image: rivet-engine:local - imagePullPolicy: Never - livenessProbe: - failureThreshold: 3 - httpGet: - path: /health - port: 6421 - periodSeconds: 10 - timeoutSeconds: 5 - name: rivet-engine - ports: - - containerPort: 6421 - name: api-peer - protocol: TCP - readinessProbe: - failureThreshold: 2 - httpGet: - path: /health - port: 6421 - periodSeconds: 5 - timeoutSeconds: 3 - resources: - limits: - cpu: 4000m - memory: 8Gi - requests: - cpu: 2000m - memory: 4Gi - startupProbe: - failureThreshold: 30 - httpGet: - path: /health - port: 6421 - initialDelaySeconds: 30 - periodSeconds: 10 - timeoutSeconds: 5 - volumeMounts: - - mountPath: /etc/rivet - name: config - readOnly: true - serviceAccountName: rivet-engine - volumes: - - configMap: - name: engine-config - name: config diff --git a/package.json b/package.json index ea8257383f..d3fcfe53ff 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,9 @@ "zx": "^8.8.5" }, "dependencies": { - "@sentry/vite-plugin": "^2.23.1" + "@iarna/toml": "^2.2.5", + "@sentry/vite-plugin": "^2.23.1", + "fast-glob": "^3.3.3" }, "resolutions": { "rivetkit": "workspace:*", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 48c6faafea..4d69416d0b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,9 +14,15 @@ importers: .: dependencies: + '@iarna/toml': + specifier: ^2.2.5 + version: 2.2.5 '@sentry/vite-plugin': specifier: ^2.23.1 version: 2.23.1 + fast-glob: + specifier: ^3.3.3 + version: 3.3.3 devDependencies: '@bare-ts/tools': specifier: 0.15.0 @@ -357,7 +363,7 @@ importers: version: 9.6.1 freestyle-sandboxes: specifier: ^0.0.95 - version: 0.0.95(expo-constants@18.0.10)(expo-linking@7.0.5)(expo@54.0.18)(react-dom@18.3.1(react@18.3.1))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native-screens@4.17.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(ws@8.18.3) + version: 0.0.95(expo-constants@18.0.10)(expo-linking@7.0.5)(expo@54.0.18)(react-dom@18.3.1(react@18.3.1))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native-screens@4.17.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(ws@8.18.3) hono: specifier: ^4.6.0 version: 4.9.8 @@ -1916,7 +1922,7 @@ importers: version: 3.13.12(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@uiw/codemirror-extensions-basic-setup': specifier: ^4.25.1 - version: 4.25.1(@codemirror/autocomplete@6.19.0)(@codemirror/commands@6.9.0)(@codemirror/language@6.11.3)(@codemirror/lint@6.9.0)(@codemirror/search@6.5.11)(@codemirror/state@6.5.2)(@codemirror/view@6.38.2) + version: 4.25.1(@codemirror/autocomplete@6.19.0)(@codemirror/commands@6.8.1)(@codemirror/language@6.11.3)(@codemirror/lint@6.9.0)(@codemirror/search@6.5.11)(@codemirror/state@6.5.2)(@codemirror/view@6.38.2) '@uiw/codemirror-theme-github': specifier: ^4.25.1 version: 4.25.1(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.38.2) @@ -2311,7 +2317,7 @@ importers: version: 5.9.2 vite-tsconfig-paths: specifier: ^5.1.4 - version: 5.1.4(typescript@5.9.2)(vite@7.2.2(@types/node@22.18.1)(jiti@1.21.7)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.44.0)(tsx@4.20.5)(yaml@2.8.1)) + version: 5.1.4(typescript@5.9.2)(vite@5.4.20(@types/node@22.18.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.44.0)) vitest: specifier: ^3.1.1 version: 3.2.4(@types/debug@4.1.12)(@types/node@22.18.1)(@vitest/ui@3.1.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.44.0) @@ -2396,7 +2402,7 @@ importers: version: 15.5.6(@mdx-js/loader@3.1.1(webpack@5.101.3(esbuild@0.25.9)))(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@19.2.0)) '@next/third-parties': specifier: latest - version: 16.0.3(next@15.5.6(@babel/core@7.28.5)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(sass@1.93.2))(react@19.2.0) + version: 16.0.9(next@15.5.6(@babel/core@7.28.5)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(sass@1.93.2))(react@19.2.0) '@rivet-gg/api': specifier: 25.5.3 version: 25.5.3 @@ -4832,6 +4838,9 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} + '@iarna/toml@2.2.5': + resolution: {integrity: sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==} + '@img/colour@1.0.0': resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} engines: {node: '>=18'} @@ -5500,8 +5509,8 @@ packages: cpu: [x64] os: [win32] - '@next/third-parties@16.0.3': - resolution: {integrity: sha512-QjTRQ4ydXguFkpMCUMl5oSslxmh8mAtmnzc9DEtLkZcGmAuQcZg2M3lswMn62sdID+F06crS3IQ58X3sjjBLVA==} + '@next/third-parties@16.0.9': + resolution: {integrity: sha512-fTK5wajWlSxCf0CDCEibt5cCxscQNffn9Sh1oLXWcDH58/IEJaFIebTf7/Vf2EHFu0iIA2qPXvgwh9kCIZjZNA==} peerDependencies: next: ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0-beta.0 react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 @@ -11510,6 +11519,7 @@ packages: next@15.4.5: resolution: {integrity: sha512-nJ4v+IO9CPmbmcvsPebIoX3Q+S7f6Fu08/dEWu0Ttfa+wVwQRh9epcmsyCPjmL2b8MxC+CkBR97jgDhUUztI3g==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} + deprecated: This version has a security vulnerability. Please upgrade to a patched version. See https://nextjs.org/blog/CVE-2025-66478 for more details. hasBin: true peerDependencies: '@opentelemetry/api': ^1.1.0 @@ -11531,6 +11541,7 @@ packages: next@15.5.6: resolution: {integrity: sha512-zTxsnI3LQo3c9HSdSf91O1jMNsEzIXDShXd4wVdg9y5shwLqBXi4ZtUUJyB86KGVSJLZx0PFONvO54aheGX8QQ==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} + deprecated: This version has a security vulnerability. Please upgrade to a patched version. See https://nextjs.org/blog/CVE-2025-66478 for more details. hasBin: true peerDependencies: '@opentelemetry/api': ^1.1.0 @@ -14681,29 +14692,29 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-create-class-features-plugin@7.28.5(@babel/core@7.28.5)': + '@babel/helper-create-class-features-plugin@7.28.5(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-member-expression-to-functions': 7.28.5 '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.5) + '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.4) '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 '@babel/traverse': 7.28.5 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.28.5)': + '@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-annotate-as-pure': 7.27.3 regexpu-core: 6.4.0 semver: 6.3.1 - '@babel/helper-define-polyfill-provider@0.6.5(@babel/core@7.28.5)': + '@babel/helper-define-polyfill-provider@0.6.5(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-compilation-targets': 7.27.2 '@babel/helper-plugin-utils': 7.27.1 debug: 4.4.3 @@ -14759,9 +14770,9 @@ snapshots: '@babel/helper-plugin-utils@7.27.1': {} - '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.28.5)': + '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-wrap-function': 7.28.3 '@babel/traverse': 7.28.5 @@ -14777,15 +14788,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-replace-supers@7.27.1(@babel/core@7.28.5)': - dependencies: - '@babel/core': 7.28.5 - '@babel/helper-member-expression-to-functions': 7.27.1 - '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/traverse': 7.28.4 - transitivePeerDependencies: - - supports-color - '@babel/helper-skip-transparent-expression-wrappers@7.27.1': dependencies: '@babel/traverse': 7.28.4 @@ -14829,73 +14831,73 @@ snapshots: dependencies: '@babel/types': 7.28.5 - '@babel/plugin-proposal-decorators@7.28.0(@babel/core@7.28.5)': + '@babel/plugin-proposal-decorators@7.28.0(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5) + '@babel/core': 7.28.4 + '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-decorators': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-syntax-decorators': 7.27.1(@babel/core@7.28.4) transitivePeerDependencies: - supports-color - '@babel/plugin-proposal-export-default-from@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-proposal-export-default-from@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.28.5)': + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.28.5)': + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.28.5)': + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.28.5)': + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-decorators@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-syntax-decorators@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.28.5)': + '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-export-default-from@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-syntax-export-default-from@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-flow@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-syntax-flow@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.5)': + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.28.5)': + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.4)': @@ -14903,49 +14905,44 @@ snapshots: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.28.5)': - dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.28.5)': + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.28.5)': + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.28.5)': + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.28.5)': + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.28.5)': + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.28.5)': + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.28.5)': + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.4)': @@ -14953,117 +14950,112 @@ snapshots: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.28.5)': - dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-async-generator-functions@7.28.0(@babel/core@7.28.5)': + '@babel/plugin-transform-async-generator-functions@7.28.0(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.5) + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.4) '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-async-to-generator@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-async-to-generator@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-module-imports': 7.27.1 '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.5) + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.4) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-block-scoping@7.28.5(@babel/core@7.28.5)': + '@babel/plugin-transform-block-scoping@7.28.5(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-class-properties@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-class-properties@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5) + '@babel/core': 7.28.4 + '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-class-static-block@7.28.3(@babel/core@7.28.5)': + '@babel/plugin-transform-class-static-block@7.28.3(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5) + '@babel/core': 7.28.4 + '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-classes@7.28.4(@babel/core@7.28.5)': + '@babel/plugin-transform-classes@7.28.4(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-compilation-targets': 7.27.2 '@babel/helper-globals': 7.28.0 '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.5) + '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.4) '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-computed-properties@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-computed-properties@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 '@babel/template': 7.27.2 - '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.28.5)': + '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-flow-strip-types@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-flow-strip-types@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-flow': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-syntax-flow': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-compilation-targets': 7.27.2 '@babel/helper-plugin-utils': 7.27.1 '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-literals@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-literals@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-logical-assignment-operators@7.28.5(@babel/core@7.28.5)': + '@babel/plugin-transform-logical-assignment-operators@7.28.5(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-transform-modules-commonjs@7.27.1(@babel/core@7.28.4)': @@ -15074,85 +15066,77 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-commonjs@7.27.1(@babel/core@7.28.5)': - dependencies: - '@babel/core': 7.28.5 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-named-capturing-groups-regex@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-named-capturing-groups-regex@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.5) + '@babel/core': 7.28.4 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-nullish-coalescing-operator@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-nullish-coalescing-operator@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-numeric-separator@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-numeric-separator@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-object-rest-spread@7.28.4(@babel/core@7.28.5)': + '@babel/plugin-transform-object-rest-spread@7.28.4(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-compilation-targets': 7.27.2 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.5) - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.5) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.4) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.4) '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-optional-catch-binding@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-optional-catch-binding@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-optional-chaining@7.28.5(@babel/core@7.28.5)': + '@babel/plugin-transform-optional-chaining@7.28.5(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.28.5)': + '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-private-methods@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-private-methods@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5) + '@babel/core': 7.28.4 + '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-private-property-in-object@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-private-property-in-object@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5) + '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-display-name@7.28.0(@babel/core@7.28.5)': + '@babel/plugin-transform-react-display-name@7.28.0(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-react-jsx-development@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-react-jsx-development@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 - '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.28.5) + '@babel/core': 7.28.4 + '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.28.4) transitivePeerDependencies: - supports-color @@ -15161,71 +15145,61 @@ snapshots: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.5)': - dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.28.5)': - dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-module-imports': 7.27.1 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.4) '@babel/types': 7.28.5 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-pure-annotations@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-react-pure-annotations@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-regenerator@7.28.4(@babel/core@7.28.5)': + '@babel/plugin-transform-regenerator@7.28.4(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-runtime@7.28.5(@babel/core@7.28.5)': + '@babel/plugin-transform-runtime@7.28.5(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-module-imports': 7.27.1 '@babel/helper-plugin-utils': 7.27.1 - babel-plugin-polyfill-corejs2: 0.4.14(@babel/core@7.28.5) - babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.28.5) - babel-plugin-polyfill-regenerator: 0.6.5(@babel/core@7.28.5) + babel-plugin-polyfill-corejs2: 0.4.14(@babel/core@7.28.4) + babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.28.4) + babel-plugin-polyfill-regenerator: 0.6.5(@babel/core@7.28.4) semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-spread@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-spread@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-transform-typescript@7.28.0(@babel/core@7.28.4)': @@ -15239,32 +15213,32 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-typescript@7.28.5(@babel/core@7.28.5)': + '@babel/plugin-transform-typescript@7.28.5(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5) + '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.4) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.5) + '@babel/core': 7.28.4 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 - '@babel/preset-react@7.28.5(@babel/core@7.28.5)': + '@babel/preset-react@7.28.5(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.28.5) - '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-react-pure-annotations': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.28.4) + '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-react-pure-annotations': 7.27.1(@babel/core@7.28.4) transitivePeerDependencies: - supports-color @@ -15279,14 +15253,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/preset-typescript@7.28.5(@babel/core@7.28.5)': + '@babel/preset-typescript@7.28.5(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-typescript': 7.28.5(@babel/core@7.28.5) + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-typescript': 7.28.5(@babel/core@7.28.4) transitivePeerDependencies: - supports-color @@ -16280,7 +16254,7 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 - '@expo/cli@54.0.13(expo-router@4.0.21)(expo@54.0.18)(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))': + '@expo/cli@54.0.13(expo-router@4.0.21)(expo@54.0.18)(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))': dependencies: '@0no-co/graphql.web': 1.2.0 '@expo/code-signing-certificates': 0.0.5 @@ -16315,7 +16289,7 @@ snapshots: connect: 3.7.0 debug: 4.4.3 env-editor: 0.4.2 - expo: 54.0.18(@babel/core@7.28.5)(@expo/metro-runtime@4.0.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1)))(expo-router@4.0.21)(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + expo: 54.0.18(@babel/core@7.28.4)(@expo/metro-runtime@4.0.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1)))(expo-router@4.0.21)(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) expo-server: 1.0.4 freeport-async: 2.0.0 getenv: 2.0.0 @@ -16348,8 +16322,8 @@ snapshots: wrap-ansi: 7.0.0 ws: 8.18.3 optionalDependencies: - expo-router: 4.0.21(expo-constants@18.0.10)(expo-linking@7.0.5)(expo@54.0.18)(react-dom@18.3.1(react@18.3.1))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native-screens@4.17.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) - react-native: 0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1) + expo-router: 4.0.21(expo-constants@18.0.10)(expo-linking@7.0.5)(expo@54.0.18)(react-dom@18.3.1(react@18.3.1))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native-screens@4.17.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + react-native: 0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1) transitivePeerDependencies: - '@modelcontextprotocol/sdk' - bufferutil @@ -16448,12 +16422,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@expo/devtools@0.1.7(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1)': + '@expo/devtools@0.1.7(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1)': dependencies: chalk: 4.1.2 optionalDependencies: react: 18.3.1 - react-native: 0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1) + react-native: 0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1) '@expo/env@0.4.2': dependencies: @@ -16553,15 +16527,15 @@ snapshots: postcss: 8.4.49 resolve-from: 5.0.0 optionalDependencies: - expo: 54.0.18(@babel/core@7.28.5)(@expo/metro-runtime@4.0.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1)))(expo-router@4.0.21)(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + expo: 54.0.18(@babel/core@7.28.4)(@expo/metro-runtime@4.0.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1)))(expo-router@4.0.21)(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - '@expo/metro-runtime@4.0.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))': + '@expo/metro-runtime@4.0.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))': dependencies: - react-native: 0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1) + react-native: 0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1) '@expo/metro@54.1.0': dependencies: @@ -16617,7 +16591,7 @@ snapshots: '@expo/json-file': 10.0.7 '@react-native/normalize-colors': 0.81.5 debug: 4.4.3 - expo: 54.0.18(@babel/core@7.28.5)(@expo/metro-runtime@4.0.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1)))(expo-router@4.0.21)(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + expo: 54.0.18(@babel/core@7.28.4)(@expo/metro-runtime@4.0.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1)))(expo-router@4.0.21)(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) resolve-from: 5.0.0 semver: 7.7.3 xml2js: 0.6.0 @@ -16643,11 +16617,11 @@ snapshots: '@expo/sudo-prompt@9.3.2': {} - '@expo/vector-icons@15.0.3(expo-font@14.0.9(expo@54.0.18)(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1)': + '@expo/vector-icons@15.0.3(expo-font@14.0.9(expo@54.0.18)(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1)': dependencies: - expo-font: 14.0.9(expo@54.0.18)(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + expo-font: 14.0.9(expo@54.0.18)(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) react: 18.3.1 - react-native: 0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1) + react-native: 0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1) '@expo/ws-tunnel@1.0.6': {} @@ -16814,6 +16788,8 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} + '@iarna/toml@2.2.5': {} + '@img/colour@1.0.0': {} '@img/sharp-darwin-arm64@0.33.5': @@ -17552,7 +17528,7 @@ snapshots: '@next/swc-win32-x64-msvc@15.5.6': optional: true - '@next/third-parties@16.0.3(next@15.5.6(@babel/core@7.28.5)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(sass@1.93.2))(react@19.2.0)': + '@next/third-parties@16.0.9(next@15.5.6(@babel/core@7.28.5)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(sass@1.93.2))(react@19.2.0)': dependencies: next: 15.5.6(@babel/core@7.28.5)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(sass@1.93.2) react: 19.2.0 @@ -18337,67 +18313,67 @@ snapshots: '@react-native/assets-registry@0.82.1': {} - '@react-native/babel-plugin-codegen@0.81.5(@babel/core@7.28.5)': + '@react-native/babel-plugin-codegen@0.81.5(@babel/core@7.28.4)': dependencies: '@babel/traverse': 7.28.5 - '@react-native/codegen': 0.81.5(@babel/core@7.28.5) + '@react-native/codegen': 0.81.5(@babel/core@7.28.4) transitivePeerDependencies: - '@babel/core' - supports-color - '@react-native/babel-preset@0.81.5(@babel/core@7.28.5)': + '@react-native/babel-preset@0.81.5(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 - '@babel/plugin-proposal-export-default-from': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.28.5) - '@babel/plugin-syntax-export-default-from': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.28.5) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.28.5) - '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-async-generator-functions': 7.28.0(@babel/core@7.28.5) - '@babel/plugin-transform-async-to-generator': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-block-scoping': 7.28.5(@babel/core@7.28.5) - '@babel/plugin-transform-class-properties': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-classes': 7.28.4(@babel/core@7.28.5) - '@babel/plugin-transform-computed-properties': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.5) - '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-logical-assignment-operators': 7.28.5(@babel/core@7.28.5) - '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-named-capturing-groups-regex': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-nullish-coalescing-operator': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-numeric-separator': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-object-rest-spread': 7.28.4(@babel/core@7.28.5) - '@babel/plugin-transform-optional-catch-binding': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-optional-chaining': 7.28.5(@babel/core@7.28.5) - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.5) - '@babel/plugin-transform-private-methods': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-private-property-in-object': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.28.5) - '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-regenerator': 7.28.4(@babel/core@7.28.5) - '@babel/plugin-transform-runtime': 7.28.5(@babel/core@7.28.5) - '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-spread': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-typescript': 7.28.5(@babel/core@7.28.5) - '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.28.5) + '@babel/core': 7.28.4 + '@babel/plugin-proposal-export-default-from': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.28.4) + '@babel/plugin-syntax-export-default-from': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.28.4) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.28.4) + '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-async-generator-functions': 7.28.0(@babel/core@7.28.4) + '@babel/plugin-transform-async-to-generator': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-block-scoping': 7.28.5(@babel/core@7.28.4) + '@babel/plugin-transform-class-properties': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-classes': 7.28.4(@babel/core@7.28.4) + '@babel/plugin-transform-computed-properties': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.4) + '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-logical-assignment-operators': 7.28.5(@babel/core@7.28.4) + '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-named-capturing-groups-regex': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-nullish-coalescing-operator': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-numeric-separator': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-object-rest-spread': 7.28.4(@babel/core@7.28.4) + '@babel/plugin-transform-optional-catch-binding': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-optional-chaining': 7.28.5(@babel/core@7.28.4) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.4) + '@babel/plugin-transform-private-methods': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-private-property-in-object': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.28.4) + '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-regenerator': 7.28.4(@babel/core@7.28.4) + '@babel/plugin-transform-runtime': 7.28.5(@babel/core@7.28.4) + '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-spread': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-typescript': 7.28.5(@babel/core@7.28.4) + '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.28.4) '@babel/template': 7.27.2 - '@react-native/babel-plugin-codegen': 0.81.5(@babel/core@7.28.5) + '@react-native/babel-plugin-codegen': 0.81.5(@babel/core@7.28.4) babel-plugin-syntax-hermes-parser: 0.29.1 - babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.28.5) + babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.28.4) react-refresh: 0.14.2 transitivePeerDependencies: - supports-color - '@react-native/codegen@0.81.5(@babel/core@7.28.5)': + '@react-native/codegen@0.81.5(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/parser': 7.28.5 glob: 7.2.3 hermes-parser: 0.29.1 @@ -18405,9 +18381,9 @@ snapshots: nullthrows: 1.1.1 yargs: 17.7.2 - '@react-native/codegen@0.82.1(@babel/core@7.28.5)': + '@react-native/codegen@0.82.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/parser': 7.28.5 glob: 7.2.3 hermes-parser: 0.32.0 @@ -18483,37 +18459,37 @@ snapshots: '@react-native/normalize-colors@0.82.1': {} - '@react-native/virtualized-lists@0.82.1(@types/react@19.2.2)(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1)': + '@react-native/virtualized-lists@0.82.1(@types/react@19.2.2)(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1)': dependencies: invariant: 2.2.4 nullthrows: 1.1.1 react: 18.3.1 - react-native: 0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1) + react-native: 0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1) optionalDependencies: '@types/react': 19.2.2 - '@react-navigation/bottom-tabs@7.4.9(@react-navigation/native@7.1.18(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native-screens@4.17.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1)': + '@react-navigation/bottom-tabs@7.4.9(@react-navigation/native@7.1.18(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native-screens@4.17.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1)': dependencies: - '@react-navigation/elements': 2.6.5(@react-navigation/native@7.1.18(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) - '@react-navigation/native': 7.1.18(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + '@react-navigation/elements': 2.6.5(@react-navigation/native@7.1.18(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + '@react-navigation/native': 7.1.18(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) color: 4.2.3 react: 18.3.1 - react-native: 0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1) - react-native-safe-area-context: 5.6.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) - react-native-screens: 4.17.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + react-native: 0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1) + react-native-safe-area-context: 5.6.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + react-native-screens: 4.17.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) transitivePeerDependencies: - '@react-native-masked-view/masked-view' optional: true - '@react-navigation/bottom-tabs@7.4.9(@react-navigation/native@7.1.18(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native-screens@4.17.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0)': + '@react-navigation/bottom-tabs@7.4.9(@react-navigation/native@7.1.18(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native-screens@4.17.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0)': dependencies: - '@react-navigation/elements': 2.6.5(@react-navigation/native@7.1.18(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0) - '@react-navigation/native': 7.1.18(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + '@react-navigation/elements': 2.6.5(@react-navigation/native@7.1.18(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0) + '@react-navigation/native': 7.1.18(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) color: 4.2.3 react: 19.2.0 - react-native: 0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1) - react-native-safe-area-context: 5.6.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) - react-native-screens: 4.17.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + react-native: 0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1) + react-native-safe-area-context: 5.6.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + react-native-screens: 4.17.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) transitivePeerDependencies: - '@react-native-masked-view/masked-view' @@ -18528,60 +18504,60 @@ snapshots: use-latest-callback: 0.2.6(react@19.2.0) use-sync-external-store: 1.6.0(react@19.2.0) - '@react-navigation/elements@2.6.5(@react-navigation/native@7.1.18(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1)': + '@react-navigation/elements@2.6.5(@react-navigation/native@7.1.18(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1)': dependencies: - '@react-navigation/native': 7.1.18(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + '@react-navigation/native': 7.1.18(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) color: 4.2.3 react: 18.3.1 - react-native: 0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1) - react-native-safe-area-context: 5.6.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + react-native: 0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1) + react-native-safe-area-context: 5.6.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) use-latest-callback: 0.2.6(react@18.3.1) use-sync-external-store: 1.6.0(react@18.3.1) optional: true - '@react-navigation/elements@2.6.5(@react-navigation/native@7.1.18(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0)': + '@react-navigation/elements@2.6.5(@react-navigation/native@7.1.18(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0)': dependencies: - '@react-navigation/native': 7.1.18(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + '@react-navigation/native': 7.1.18(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) color: 4.2.3 react: 19.2.0 - react-native: 0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1) - react-native-safe-area-context: 5.6.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + react-native: 0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1) + react-native-safe-area-context: 5.6.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) use-latest-callback: 0.2.6(react@19.2.0) use-sync-external-store: 1.6.0(react@19.2.0) - '@react-navigation/native-stack@7.3.28(@react-navigation/native@7.1.18(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native-screens@4.17.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1)': + '@react-navigation/native-stack@7.3.28(@react-navigation/native@7.1.18(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native-screens@4.17.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1)': dependencies: - '@react-navigation/elements': 2.6.5(@react-navigation/native@7.1.18(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) - '@react-navigation/native': 7.1.18(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + '@react-navigation/elements': 2.6.5(@react-navigation/native@7.1.18(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + '@react-navigation/native': 7.1.18(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) react: 18.3.1 - react-native: 0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1) - react-native-safe-area-context: 5.6.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) - react-native-screens: 4.17.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + react-native: 0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1) + react-native-safe-area-context: 5.6.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + react-native-screens: 4.17.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) warn-once: 0.1.1 transitivePeerDependencies: - '@react-native-masked-view/masked-view' optional: true - '@react-navigation/native-stack@7.3.28(@react-navigation/native@7.1.18(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native-screens@4.17.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0)': + '@react-navigation/native-stack@7.3.28(@react-navigation/native@7.1.18(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native-screens@4.17.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0)': dependencies: - '@react-navigation/elements': 2.6.5(@react-navigation/native@7.1.18(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0) - '@react-navigation/native': 7.1.18(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + '@react-navigation/elements': 2.6.5(@react-navigation/native@7.1.18(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0) + '@react-navigation/native': 7.1.18(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) react: 19.2.0 - react-native: 0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1) - react-native-safe-area-context: 5.6.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) - react-native-screens: 4.17.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + react-native: 0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1) + react-native-safe-area-context: 5.6.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + react-native-screens: 4.17.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) warn-once: 0.1.1 transitivePeerDependencies: - '@react-native-masked-view/masked-view' - '@react-navigation/native@7.1.18(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1)': + '@react-navigation/native@7.1.18(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1)': dependencies: '@react-navigation/core': 7.12.4(react@19.2.0) escape-string-regexp: 4.0.0 fast-deep-equal: 3.1.3 nanoid: 3.3.11 react: 18.3.1 - react-native: 0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1) + react-native: 0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1) use-latest-callback: 0.2.6(react@19.2.0) '@react-navigation/routers@7.5.1': @@ -19852,16 +19828,6 @@ snapshots: '@codemirror/state': 6.5.2 '@codemirror/view': 6.38.2 - '@uiw/codemirror-extensions-basic-setup@4.25.1(@codemirror/autocomplete@6.19.0)(@codemirror/commands@6.9.0)(@codemirror/language@6.11.3)(@codemirror/lint@6.9.0)(@codemirror/search@6.5.11)(@codemirror/state@6.5.2)(@codemirror/view@6.38.2)': - dependencies: - '@codemirror/autocomplete': 6.19.0 - '@codemirror/commands': 6.9.0 - '@codemirror/language': 6.11.3 - '@codemirror/lint': 6.9.0 - '@codemirror/search': 6.5.11 - '@codemirror/state': 6.5.2 - '@codemirror/view': 6.38.2 - '@uiw/codemirror-theme-github@4.25.1(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.38.2)': dependencies: '@uiw/codemirror-themes': 4.25.1(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.38.2) @@ -20702,13 +20668,13 @@ snapshots: transitivePeerDependencies: - supports-color - babel-jest@29.7.0(@babel/core@7.28.5): + babel-jest@29.7.0(@babel/core@7.28.4): dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@jest/transform': 29.7.0 '@types/babel__core': 7.20.5 babel-plugin-istanbul: 6.1.1 - babel-preset-jest: 29.6.3(@babel/core@7.28.5) + babel-preset-jest: 29.6.3(@babel/core@7.28.4) chalk: 4.1.2 graceful-fs: 4.2.11 slash: 3.0.0 @@ -20738,27 +20704,27 @@ snapshots: cosmiconfig: 7.1.0 resolve: 1.22.11 - babel-plugin-polyfill-corejs2@0.4.14(@babel/core@7.28.5): + babel-plugin-polyfill-corejs2@0.4.14(@babel/core@7.28.4): dependencies: '@babel/compat-data': 7.28.5 - '@babel/core': 7.28.5 - '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.28.5) + '@babel/core': 7.28.4 + '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.28.4) semver: 6.3.1 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.28.5): + babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.28.4): dependencies: - '@babel/core': 7.28.5 - '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.28.5) + '@babel/core': 7.28.4 + '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.28.4) core-js-compat: 3.47.0 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-regenerator@0.6.5(@babel/core@7.28.5): + babel-plugin-polyfill-regenerator@0.6.5(@babel/core@7.28.4): dependencies: - '@babel/core': 7.28.5 - '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.28.5) + '@babel/core': 7.28.4 + '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.28.4) transitivePeerDependencies: - supports-color @@ -20776,68 +20742,68 @@ snapshots: dependencies: hermes-parser: 0.32.0 - babel-plugin-transform-flow-enums@0.0.2(@babel/core@7.28.5): + babel-plugin-transform-flow-enums@0.0.2(@babel/core@7.28.4): dependencies: - '@babel/plugin-syntax-flow': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-syntax-flow': 7.27.1(@babel/core@7.28.4) transitivePeerDependencies: - '@babel/core' - babel-preset-current-node-syntax@1.2.0(@babel/core@7.28.5): + babel-preset-current-node-syntax@1.2.0(@babel/core@7.28.4): dependencies: - '@babel/core': 7.28.5 - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.28.5) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.28.5) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.28.5) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.28.5) - '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.5) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.28.5) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.28.5) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.28.5) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.28.5) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.28.5) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.28.5) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.28.5) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.28.5) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.28.5) - - babel-preset-expo@54.0.7(@babel/core@7.28.5)(@babel/runtime@7.28.4)(expo@54.0.18)(react-refresh@0.14.2): + '@babel/core': 7.28.4 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.28.4) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.28.4) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.28.4) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.28.4) + '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.4) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.28.4) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.28.4) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.28.4) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.28.4) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.28.4) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.28.4) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.28.4) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.28.4) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.28.4) + + babel-preset-expo@54.0.7(@babel/core@7.28.4)(@babel/runtime@7.28.4)(expo@54.0.18)(react-refresh@0.14.2): dependencies: '@babel/helper-module-imports': 7.27.1 - '@babel/plugin-proposal-decorators': 7.28.0(@babel/core@7.28.5) - '@babel/plugin-proposal-export-default-from': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-syntax-export-default-from': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-class-static-block': 7.28.3(@babel/core@7.28.5) - '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-object-rest-spread': 7.28.4(@babel/core@7.28.5) - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.5) - '@babel/plugin-transform-private-methods': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-private-property-in-object': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-runtime': 7.28.5(@babel/core@7.28.5) - '@babel/preset-react': 7.28.5(@babel/core@7.28.5) - '@babel/preset-typescript': 7.28.5(@babel/core@7.28.5) - '@react-native/babel-preset': 0.81.5(@babel/core@7.28.5) + '@babel/plugin-proposal-decorators': 7.28.0(@babel/core@7.28.4) + '@babel/plugin-proposal-export-default-from': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-syntax-export-default-from': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-class-static-block': 7.28.3(@babel/core@7.28.4) + '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-object-rest-spread': 7.28.4(@babel/core@7.28.4) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.4) + '@babel/plugin-transform-private-methods': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-private-property-in-object': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-runtime': 7.28.5(@babel/core@7.28.4) + '@babel/preset-react': 7.28.5(@babel/core@7.28.4) + '@babel/preset-typescript': 7.28.5(@babel/core@7.28.4) + '@react-native/babel-preset': 0.81.5(@babel/core@7.28.4) babel-plugin-react-compiler: 1.0.0 babel-plugin-react-native-web: 0.21.2 babel-plugin-syntax-hermes-parser: 0.29.1 - babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.28.5) + babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.28.4) debug: 4.4.3 react-refresh: 0.14.2 resolve-from: 5.0.0 optionalDependencies: '@babel/runtime': 7.28.4 - expo: 54.0.18(@babel/core@7.28.5)(@expo/metro-runtime@4.0.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1)))(expo-router@4.0.21)(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + expo: 54.0.18(@babel/core@7.28.4)(@expo/metro-runtime@4.0.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1)))(expo-router@4.0.21)(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) transitivePeerDependencies: - '@babel/core' - supports-color - babel-preset-jest@29.6.3(@babel/core@7.28.5): + babel-preset-jest@29.6.3(@babel/core@7.28.4): dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 babel-plugin-jest-hoist: 29.6.3 - babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.5) + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.4) bail@2.0.2: {} @@ -22078,7 +22044,7 @@ snapshots: eslint: 9.39.1(jiti@1.21.7) eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7)))(eslint@9.39.1(jiti@1.21.7)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7)))(eslint@9.39.1(jiti@1.21.7)))(eslint@9.39.1(jiti@1.21.7)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@1.21.7)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.1(jiti@1.21.7)) eslint-plugin-react: 7.37.5(eslint@9.39.1(jiti@1.21.7)) eslint-plugin-react-hooks: 7.0.1(eslint@9.39.1(jiti@1.21.7)) @@ -22111,7 +22077,7 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7)))(eslint@9.39.1(jiti@1.21.7)))(eslint@9.39.1(jiti@1.21.7)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@1.21.7)) transitivePeerDependencies: - supports-color @@ -22126,7 +22092,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7)))(eslint@9.39.1(jiti@1.21.7)))(eslint@9.39.1(jiti@1.21.7)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@1.21.7)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -22387,57 +22353,57 @@ snapshots: expect-type@1.2.2: {} - expo-asset@12.0.10(expo@54.0.18)(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1): + expo-asset@12.0.10(expo@54.0.18)(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1): dependencies: '@expo/image-utils': 0.8.7 - expo: 54.0.18(@babel/core@7.28.5)(@expo/metro-runtime@4.0.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1)))(expo-router@4.0.21)(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) - expo-constants: 18.0.10(expo@54.0.18)(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1)) + expo: 54.0.18(@babel/core@7.28.4)(@expo/metro-runtime@4.0.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1)))(expo-router@4.0.21)(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + expo-constants: 18.0.10(expo@54.0.18)(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1)) react: 18.3.1 - react-native: 0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1) + react-native: 0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1) transitivePeerDependencies: - supports-color - expo-constants@17.0.8(expo@54.0.18)(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1)): + expo-constants@17.0.8(expo@54.0.18)(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1)): dependencies: '@expo/config': 10.0.11 '@expo/env': 0.4.2 - expo: 54.0.18(@babel/core@7.28.5)(@expo/metro-runtime@4.0.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1)))(expo-router@4.0.21)(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) - react-native: 0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1) + expo: 54.0.18(@babel/core@7.28.4)(@expo/metro-runtime@4.0.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1)))(expo-router@4.0.21)(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + react-native: 0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1) transitivePeerDependencies: - supports-color - expo-constants@18.0.10(expo@54.0.18)(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1)): + expo-constants@18.0.10(expo@54.0.18)(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1)): dependencies: '@expo/config': 12.0.10 '@expo/env': 2.0.7 - expo: 54.0.18(@babel/core@7.28.5)(@expo/metro-runtime@4.0.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1)))(expo-router@4.0.21)(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) - react-native: 0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1) + expo: 54.0.18(@babel/core@7.28.4)(@expo/metro-runtime@4.0.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1)))(expo-router@4.0.21)(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + react-native: 0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1) transitivePeerDependencies: - supports-color - expo-file-system@19.0.19(expo@54.0.18)(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1)): + expo-file-system@19.0.19(expo@54.0.18)(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1)): dependencies: - expo: 54.0.18(@babel/core@7.28.5)(@expo/metro-runtime@4.0.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1)))(expo-router@4.0.21)(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) - react-native: 0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1) + expo: 54.0.18(@babel/core@7.28.4)(@expo/metro-runtime@4.0.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1)))(expo-router@4.0.21)(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + react-native: 0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1) - expo-font@14.0.9(expo@54.0.18)(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1): + expo-font@14.0.9(expo@54.0.18)(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1): dependencies: - expo: 54.0.18(@babel/core@7.28.5)(@expo/metro-runtime@4.0.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1)))(expo-router@4.0.21)(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + expo: 54.0.18(@babel/core@7.28.4)(@expo/metro-runtime@4.0.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1)))(expo-router@4.0.21)(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) fontfaceobserver: 2.3.0 react: 18.3.1 - react-native: 0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1) + react-native: 0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1) expo-keep-awake@15.0.7(expo@54.0.18)(react@18.3.1): dependencies: - expo: 54.0.18(@babel/core@7.28.5)(@expo/metro-runtime@4.0.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1)))(expo-router@4.0.21)(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + expo: 54.0.18(@babel/core@7.28.4)(@expo/metro-runtime@4.0.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1)))(expo-router@4.0.21)(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) react: 18.3.1 - expo-linking@7.0.5(expo@54.0.18)(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1): + expo-linking@7.0.5(expo@54.0.18)(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1): dependencies: - expo-constants: 17.0.8(expo@54.0.18)(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1)) + expo-constants: 17.0.8(expo@54.0.18)(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1)) invariant: 2.2.4 react: 18.3.1 - react-native: 0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1) + react-native: 0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1) transitivePeerDependencies: - expo - supports-color @@ -22451,29 +22417,29 @@ snapshots: require-from-string: 2.0.2 resolve-from: 5.0.0 - expo-modules-core@3.0.22(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1): + expo-modules-core@3.0.22(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1): dependencies: invariant: 2.2.4 react: 18.3.1 - react-native: 0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1) + react-native: 0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1) - expo-router@4.0.21(expo-constants@18.0.10)(expo-linking@7.0.5)(expo@54.0.18)(react-dom@18.3.1(react@18.3.1))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native-screens@4.17.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1): + expo-router@4.0.21(expo-constants@18.0.10)(expo-linking@7.0.5)(expo@54.0.18)(react-dom@18.3.1(react@18.3.1))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native-screens@4.17.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1): dependencies: - '@expo/metro-runtime': 4.0.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1)) + '@expo/metro-runtime': 4.0.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1)) '@expo/server': 0.5.3 '@radix-ui/react-slot': 1.0.1(react@18.3.1) - '@react-navigation/bottom-tabs': 7.4.9(@react-navigation/native@7.1.18(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native-screens@4.17.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) - '@react-navigation/native': 7.1.18(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) - '@react-navigation/native-stack': 7.3.28(@react-navigation/native@7.1.18(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native-screens@4.17.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + '@react-navigation/bottom-tabs': 7.4.9(@react-navigation/native@7.1.18(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native-screens@4.17.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + '@react-navigation/native': 7.1.18(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + '@react-navigation/native-stack': 7.3.28(@react-navigation/native@7.1.18(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native-screens@4.17.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) client-only: 0.0.1 - expo: 54.0.18(@babel/core@7.28.5)(@expo/metro-runtime@4.0.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1)))(expo-router@4.0.21)(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) - expo-constants: 18.0.10(expo@54.0.18)(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1)) - expo-linking: 7.0.5(expo@54.0.18)(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + expo: 54.0.18(@babel/core@7.28.4)(@expo/metro-runtime@4.0.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1)))(expo-router@4.0.21)(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + expo-constants: 18.0.10(expo@54.0.18)(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1)) + expo-linking: 7.0.5(expo@54.0.18)(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) react-helmet-async: 1.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-native-helmet-async: 2.0.4(react@18.3.1) - react-native-is-edge-to-edge: 1.2.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) - react-native-safe-area-context: 5.6.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) - react-native-screens: 4.17.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + react-native-is-edge-to-edge: 1.2.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + react-native-safe-area-context: 5.6.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + react-native-screens: 4.17.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) schema-utils: 4.3.3 semver: 7.6.3 server-only: 0.0.1 @@ -22485,23 +22451,23 @@ snapshots: - supports-color optional: true - expo-router@4.0.21(expo-constants@18.0.10)(expo-linking@7.0.5)(expo@54.0.18)(react-dom@18.3.1(react@18.3.1))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native-screens@4.17.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0): + expo-router@4.0.21(expo-constants@18.0.10)(expo-linking@7.0.5)(expo@54.0.18)(react-dom@18.3.1(react@18.3.1))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native-screens@4.17.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0): dependencies: - '@expo/metro-runtime': 4.0.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1)) + '@expo/metro-runtime': 4.0.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1)) '@expo/server': 0.5.3 '@radix-ui/react-slot': 1.0.1(react@19.2.0) - '@react-navigation/bottom-tabs': 7.4.9(@react-navigation/native@7.1.18(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native-screens@4.17.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0) - '@react-navigation/native': 7.1.18(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) - '@react-navigation/native-stack': 7.3.28(@react-navigation/native@7.1.18(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native-screens@4.17.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0) + '@react-navigation/bottom-tabs': 7.4.9(@react-navigation/native@7.1.18(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native-screens@4.17.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0) + '@react-navigation/native': 7.1.18(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + '@react-navigation/native-stack': 7.3.28(@react-navigation/native@7.1.18(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native-screens@4.17.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0) client-only: 0.0.1 - expo: 54.0.18(@babel/core@7.28.5)(@expo/metro-runtime@4.0.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1)))(expo-router@4.0.21)(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) - expo-constants: 18.0.10(expo@54.0.18)(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1)) - expo-linking: 7.0.5(expo@54.0.18)(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + expo: 54.0.18(@babel/core@7.28.4)(@expo/metro-runtime@4.0.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1)))(expo-router@4.0.21)(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + expo-constants: 18.0.10(expo@54.0.18)(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1)) + expo-linking: 7.0.5(expo@54.0.18)(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) react-helmet-async: 1.3.0(react-dom@18.3.1(react@18.3.1))(react@19.2.0) react-native-helmet-async: 2.0.4(react@19.2.0) - react-native-is-edge-to-edge: 1.2.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0) - react-native-safe-area-context: 5.6.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) - react-native-screens: 4.17.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + react-native-is-edge-to-edge: 1.2.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0) + react-native-safe-area-context: 5.6.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + react-native-screens: 4.17.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) schema-utils: 4.3.3 semver: 7.6.3 server-only: 0.0.1 @@ -22514,33 +22480,33 @@ snapshots: expo-server@1.0.4: {} - expo@54.0.18(@babel/core@7.28.5)(@expo/metro-runtime@4.0.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1)))(expo-router@4.0.21)(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1): + expo@54.0.18(@babel/core@7.28.4)(@expo/metro-runtime@4.0.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1)))(expo-router@4.0.21)(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.28.4 - '@expo/cli': 54.0.13(expo-router@4.0.21)(expo@54.0.18)(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1)) + '@expo/cli': 54.0.13(expo-router@4.0.21)(expo@54.0.18)(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1)) '@expo/config': 12.0.10 '@expo/config-plugins': 54.0.2 - '@expo/devtools': 0.1.7(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + '@expo/devtools': 0.1.7(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) '@expo/fingerprint': 0.15.2 '@expo/metro': 54.1.0 '@expo/metro-config': 54.0.7(expo@54.0.18) - '@expo/vector-icons': 15.0.3(expo-font@14.0.9(expo@54.0.18)(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + '@expo/vector-icons': 15.0.3(expo-font@14.0.9(expo@54.0.18)(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) '@ungap/structured-clone': 1.3.0 - babel-preset-expo: 54.0.7(@babel/core@7.28.5)(@babel/runtime@7.28.4)(expo@54.0.18)(react-refresh@0.14.2) - expo-asset: 12.0.10(expo@54.0.18)(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) - expo-constants: 18.0.10(expo@54.0.18)(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1)) - expo-file-system: 19.0.19(expo@54.0.18)(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1)) - expo-font: 14.0.9(expo@54.0.18)(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + babel-preset-expo: 54.0.7(@babel/core@7.28.4)(@babel/runtime@7.28.4)(expo@54.0.18)(react-refresh@0.14.2) + expo-asset: 12.0.10(expo@54.0.18)(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + expo-constants: 18.0.10(expo@54.0.18)(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1)) + expo-file-system: 19.0.19(expo@54.0.18)(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1)) + expo-font: 14.0.9(expo@54.0.18)(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) expo-keep-awake: 15.0.7(expo@54.0.18)(react@18.3.1) expo-modules-autolinking: 3.0.18 - expo-modules-core: 3.0.22(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + expo-modules-core: 3.0.22(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) pretty-format: 29.7.0 react: 18.3.1 - react-native: 0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1) + react-native: 0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1) react-refresh: 0.14.2 whatwg-url-without-unicode: 8.0.0-3 optionalDependencies: - '@expo/metro-runtime': 4.0.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1)) + '@expo/metro-runtime': 4.0.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1)) transitivePeerDependencies: - '@babel/core' - '@modelcontextprotocol/sdk' @@ -22830,11 +22796,11 @@ snapshots: freeport-async@2.0.0: {} - freestyle-sandboxes@0.0.66(expo-constants@18.0.10)(expo-linking@7.0.5)(expo@54.0.18)(react-dom@18.3.1(react@18.3.1))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native-screens@4.17.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(ws@8.18.3): + freestyle-sandboxes@0.0.66(expo-constants@18.0.10)(expo-linking@7.0.5)(expo@54.0.18)(react-dom@18.3.1(react@18.3.1))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native-screens@4.17.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(ws@8.18.3): dependencies: '@hey-api/client-fetch': 0.5.7 '@tanstack/react-query': 5.87.1(react@19.2.0) - expo-router: 4.0.21(expo-constants@18.0.10)(expo-linking@7.0.5)(expo@54.0.18)(react-dom@18.3.1(react@18.3.1))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native-screens@4.17.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0) + expo-router: 4.0.21(expo-constants@18.0.10)(expo-linking@7.0.5)(expo@54.0.18)(react-dom@18.3.1(react@18.3.1))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native-screens@4.17.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0) glob: 11.0.3 hono: 4.9.8 openai: 4.104.0(ws@8.18.3)(zod@3.25.76) @@ -22857,13 +22823,13 @@ snapshots: - supports-color - ws - freestyle-sandboxes@0.0.95(expo-constants@18.0.10)(expo-linking@7.0.5)(expo@54.0.18)(react-dom@18.3.1(react@18.3.1))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native-screens@4.17.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(ws@8.18.3): + freestyle-sandboxes@0.0.95(expo-constants@18.0.10)(expo-linking@7.0.5)(expo@54.0.18)(react-dom@18.3.1(react@18.3.1))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native-screens@4.17.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(ws@8.18.3): dependencies: '@hey-api/client-fetch': 0.5.7 '@tanstack/react-query': 5.87.1(react@19.2.0) '@types/react': 19.2.2 - expo-router: 4.0.21(expo-constants@18.0.10)(expo-linking@7.0.5)(expo@54.0.18)(react-dom@18.3.1(react@18.3.1))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native-screens@4.17.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0) - freestyle-sandboxes: 0.0.66(expo-constants@18.0.10)(expo-linking@7.0.5)(expo@54.0.18)(react-dom@18.3.1(react@18.3.1))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native-screens@4.17.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(ws@8.18.3) + expo-router: 4.0.21(expo-constants@18.0.10)(expo-linking@7.0.5)(expo@54.0.18)(react-dom@18.3.1(react@18.3.1))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native-screens@4.17.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0) + freestyle-sandboxes: 0.0.66(expo-constants@18.0.10)(expo-linking@7.0.5)(expo@54.0.18)(react-dom@18.3.1(react@18.3.1))(react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native-screens@4.17.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1))(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(ws@8.18.3) glob: 11.0.3 hono: 4.9.8 openai: 4.104.0(ws@8.18.3)(zod@3.25.76) @@ -26039,43 +26005,43 @@ snapshots: react-fast-compare: 3.2.2 shallowequal: 1.1.0 - react-native-is-edge-to-edge@1.2.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1): + react-native-is-edge-to-edge@1.2.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1): dependencies: react: 18.3.1 - react-native: 0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1) + react-native: 0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1) optional: true - react-native-is-edge-to-edge@1.2.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0): + react-native-is-edge-to-edge@1.2.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@19.2.0): dependencies: react: 19.2.0 - react-native: 0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1) + react-native: 0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1) - react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1): + react-native-safe-area-context@5.6.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1): dependencies: react: 18.3.1 - react-native: 0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1) + react-native: 0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1) - react-native-screens@4.17.1(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1): + react-native-screens@4.17.1(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1): dependencies: react: 18.3.1 react-freeze: 1.0.4(react@18.3.1) - react-native: 0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1) + react-native: 0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1) warn-once: 0.1.1 - react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1): + react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1): dependencies: '@jest/create-cache-key-function': 29.7.0 '@react-native/assets-registry': 0.82.1 - '@react-native/codegen': 0.82.1(@babel/core@7.28.5) + '@react-native/codegen': 0.82.1(@babel/core@7.28.4) '@react-native/community-cli-plugin': 0.82.1 '@react-native/gradle-plugin': 0.82.1 '@react-native/js-polyfills': 0.82.1 '@react-native/normalize-colors': 0.82.1 - '@react-native/virtualized-lists': 0.82.1(@types/react@19.2.2)(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) + '@react-native/virtualized-lists': 0.82.1(@types/react@19.2.2)(react-native@0.82.1(@babel/core@7.28.4)(@types/react@19.2.2)(react@18.3.1))(react@18.3.1) abort-controller: 3.0.0 anser: 1.4.10 ansi-regex: 5.0.1 - babel-jest: 29.7.0(@babel/core@7.28.5) + babel-jest: 29.7.0(@babel/core@7.28.4) babel-plugin-syntax-hermes-parser: 0.32.0 base64-js: 1.5.1 commander: 12.1.0 @@ -28380,13 +28346,13 @@ snapshots: - supports-color - typescript - vite-tsconfig-paths@5.1.4(typescript@5.9.2)(vite@7.2.2(@types/node@22.18.1)(jiti@1.21.7)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.44.0)(tsx@4.20.5)(yaml@2.8.1)): + vite-tsconfig-paths@5.1.4(typescript@5.9.2)(vite@5.4.20(@types/node@22.18.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.44.0)): dependencies: debug: 4.4.1 globrex: 0.1.2 tsconfck: 3.1.6(typescript@5.9.2) optionalDependencies: - vite: 7.2.2(@types/node@22.18.1)(jiti@1.21.7)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.44.0)(tsx@4.20.5)(yaml@2.8.1) + vite: 5.4.20(@types/node@22.18.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.44.0) transitivePeerDependencies: - supports-color - typescript @@ -28501,27 +28467,6 @@ snapshots: tsx: 4.20.6 yaml: 2.8.1 - vite@7.2.2(@types/node@22.18.1)(jiti@1.21.7)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.44.0)(tsx@4.20.5)(yaml@2.8.1): - dependencies: - esbuild: 0.25.12 - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - postcss: 8.5.6 - rollup: 4.53.3 - tinyglobby: 0.2.15 - optionalDependencies: - '@types/node': 22.18.1 - fsevents: 2.3.3 - jiti: 1.21.7 - less: 4.4.1 - lightningcss: 1.30.2 - sass: 1.93.2 - stylus: 0.62.0 - terser: 5.44.0 - tsx: 4.20.5 - yaml: 2.8.1 - optional: true - vite@7.2.2(@types/node@24.10.1)(jiti@1.21.7)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1): dependencies: esbuild: 0.25.12 diff --git a/scripts/cargo/update_workspace.ts b/scripts/cargo/update_workspace.ts index 8dd3f29224..28d1f08f88 100755 --- a/scripts/cargo/update_workspace.ts +++ b/scripts/cargo/update_workspace.ts @@ -1,67 +1,54 @@ -#!/usr/bin/env -S deno run --allow-net --allow-env --allow-read --allow-write - -import { exists, walk } from "@std/fs"; -import { join, relative } from "@std/path"; -import { parse, stringify } from "@std/toml"; - -const rootDir = join(import.meta.dirname, "../.."); +#!/usr/bin/env tsx + +import { readFile, writeFile, access } from 'node:fs/promises'; +import { join, relative, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { parse, stringify } from '@iarna/toml'; +import fg from 'fast-glob'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const rootDir = join(__dirname, "../.."); + +async function exists(path: string): Promise { + try { + await access(path); + return true; + } catch { + return false; + } +} async function updateCargoToml() { const workspaceTomlPath = join(rootDir, "Cargo.toml"); - const workspaceTomlContent = await Deno.readTextFile(workspaceTomlPath); - const workspaceToml = parse(workspaceTomlContent); - - const entries = (async function* () { - // Yield from engine/packages/* (1 level deep) - for await (const entry of walk(join(rootDir, "engine", "packages"), { - includeDirs: false, - exts: ["toml"], - skip: [/node_modules/], - })) { - if (entry.path.endsWith("Cargo.toml")) { - const relativePath = relative( - join(rootDir, "engine", "packages"), - entry.path, - ); - const pathParts = relativePath.split("/"); - if (pathParts.length === 2) { - // Directly in a subdirectory - yield entry; - } - } - } + const workspaceTomlContent = await readFile(workspaceTomlPath, 'utf-8'); + const workspaceToml = parse(workspaceTomlContent) as any; + + // Find all Cargo.toml files in engine/packages/* (1 level deep) + const enginePackages = await fg('engine/packages/*/Cargo.toml', { + cwd: rootDir, + ignore: ['**/node_modules/**'], + }); + + // Find all Cargo.toml files in engine/sdks/rust/* (1 level deep) if it exists + const sdksRustDir = join(rootDir, "engine", "sdks", "rust"); + let sdkPackages: string[] = []; + if (await exists(sdksRustDir)) { + sdkPackages = await fg('engine/sdks/rust/*/Cargo.toml', { + cwd: rootDir, + ignore: ['**/node_modules/**'], + }); + } - // Yield from engine/sdks/rust/* (1 level deep) if it exists - const sdksRustDir = join(rootDir, "engine", "sdks", "rust"); - if (await exists(sdksRustDir)) { - for await (const entry of walk(sdksRustDir, { - includeDirs: false, - exts: ["toml"], - skip: [/node_modules/], - })) { - if (entry.path.endsWith("Cargo.toml")) { - const relativePath = relative(sdksRustDir, entry.path); - const pathParts = relativePath.split("/"); - if (pathParts.length === 2) { - // Directly in a subdirectory - yield entry; - } - } - } - } - })(); + const allCargoTomls = [...enginePackages, ...sdkPackages]; - // Find all workspace members + // Build list of workspace members const members: string[] = []; - for await (const entry of entries) { - const packagePath = relative( - rootDir, - entry.path.replace(/\/Cargo\.toml$/, ""), - ); + for (const cargoTomlPath of allCargoTomls) { + const packagePath = cargoTomlPath.replace(/\/Cargo\.toml$/, ""); members.push(packagePath); } - // Sort deps + // Sort members members.sort(); // Remove path dependencies, since we'll replace these. This lets us @@ -79,10 +66,11 @@ async function updateCargoToml() { "rivet-util": ["util"], gasoline: ["gas"], }; + for (const packagePath of members) { const packageTomlPath = join(rootDir, packagePath, "Cargo.toml"); - const packageTomlContent = await Deno.readTextFile(packageTomlPath); - const packageToml = parse(packageTomlContent); + const packageTomlContent = await readFile(packageTomlPath, 'utf-8'); + const packageToml = parse(packageTomlContent) as any; // Save to workspace newDependencies[packageToml.package.name] = { @@ -115,7 +103,7 @@ async function updateCargoToml() { // // Write the updated package Cargo.toml // const updatedPackageTomlContent = stringify(packageToml); - // await Deno.writeTextFile(packageTomlPath, updatedPackageTomlContent); + // await writeFile(packageTomlPath, updatedPackageTomlContent, 'utf-8'); } // Update and write workspace @@ -127,7 +115,7 @@ async function updateCargoToml() { }; const updatedTomlContent = stringify(workspaceToml); - await Deno.writeTextFile(workspaceTomlPath, updatedTomlContent); + await writeFile(workspaceTomlPath, updatedTomlContent, 'utf-8'); } updateCargoToml().catch(console.error); diff --git a/scripts/deno.json b/scripts/deno.json deleted file mode 100644 index 36f423041a..0000000000 --- a/scripts/deno.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "imports": { - "@std/assert": "jsr:@std/assert@^1.0.8", - "@std/cli": "jsr:@std/cli@^1.0.8", - "@std/yaml": "jsr:@std/yaml@^1.0.5", - "dax": "jsr:@david/dax@^0.42.0", - "@std/fs": "jsr:@std/fs@^1.0.5", - "@std/path": "jsr:@std/path@^1.0.8", - "@std/toml": "jsr:@std/toml@^1.0.1", - "dedent": "npm:dedent@^1.5.3", - "glob": "npm:glob@^11.0.0", - "type-fest": "npm:type-fest@^4.32.0" - }, - "fmt": { - "useTabs": true - } -} diff --git a/scripts/deno.lock b/scripts/deno.lock deleted file mode 100644 index ee1186d24f..0000000000 --- a/scripts/deno.lock +++ /dev/null @@ -1,307 +0,0 @@ -{ - "version": "5", - "specifiers": { - "jsr:@david/dax@0.42": "0.42.0", - "jsr:@david/path@0.2": "0.2.0", - "jsr:@david/which@~0.4.1": "0.4.1", - "jsr:@std/assert@*": "1.0.13", - "jsr:@std/assert@0.221": "0.221.0", - "jsr:@std/assert@^1.0.8": "1.0.13", - "jsr:@std/bytes@0.221": "0.221.0", - "jsr:@std/cli@*": "1.0.21", - "jsr:@std/collections@^1.1.1": "1.1.2", - "jsr:@std/fmt@1": "1.0.8", - "jsr:@std/fs@1": "1.0.19", - "jsr:@std/fs@^1.0.5": "1.0.19", - "jsr:@std/internal@^1.0.6": "1.0.9", - "jsr:@std/internal@^1.0.9": "1.0.9", - "jsr:@std/io@0.221": "0.221.0", - "jsr:@std/path@*": "1.1.1", - "jsr:@std/path@1": "1.1.1", - "jsr:@std/path@^1.0.8": "1.1.1", - "jsr:@std/path@^1.1.1": "1.1.1", - "jsr:@std/streams@0.221": "0.221.0", - "jsr:@std/toml@^1.0.1": "1.0.8", - "jsr:@std/yaml@^1.0.5": "1.0.8", - "npm:glob@11": "11.0.3" - }, - "jsr": { - "@david/dax@0.42.0": { - "integrity": "0c547c9a20577a6072b90def194c159c9ddab82280285ebfd8268a4ebefbd80b", - "dependencies": [ - "jsr:@david/path", - "jsr:@david/which", - "jsr:@std/fmt", - "jsr:@std/fs@1", - "jsr:@std/io", - "jsr:@std/path@1", - "jsr:@std/streams" - ] - }, - "@david/path@0.2.0": { - "integrity": "f2d7aa7f02ce5a55e27c09f9f1381794acb09d328f8d3c8a2e3ab3ffc294dccd", - "dependencies": [ - "jsr:@std/fs@1", - "jsr:@std/path@1" - ] - }, - "@david/which@0.4.1": { - "integrity": "896a682b111f92ab866cc70c5b4afab2f5899d2f9bde31ed00203b9c250f225e" - }, - "@std/assert@0.221.0": { - "integrity": "a5f1aa6e7909dbea271754fd4ab3f4e687aeff4873b4cef9a320af813adb489a" - }, - "@std/assert@1.0.13": { - "integrity": "ae0d31e41919b12c656c742b22522c32fb26ed0cba32975cb0de2a273cb68b29", - "dependencies": [ - "jsr:@std/internal@^1.0.6" - ] - }, - "@std/bytes@0.221.0": { - "integrity": "64a047011cf833890a4a2ab7293ac55a1b4f5a050624ebc6a0159c357de91966" - }, - "@std/cli@1.0.21": { - "integrity": "cd25b050bdf6282e321854e3822bee624f07aca7636a3a76d95f77a3a919ca2a" - }, - "@std/collections@1.1.2": { - "integrity": "f1685dd45c3ec27c39d0e8a642ccf810f391ec8a6cb5e7355926e6dacc64c43e" - }, - "@std/fmt@1.0.8": { - "integrity": "71e1fc498787e4434d213647a6e43e794af4fd393ef8f52062246e06f7e372b7" - }, - "@std/fs@1.0.19": { - "integrity": "051968c2b1eae4d2ea9f79a08a3845740ef6af10356aff43d3e2ef11ed09fb06", - "dependencies": [ - "jsr:@std/internal@^1.0.9", - "jsr:@std/path@^1.1.1" - ] - }, - "@std/internal@1.0.9": { - "integrity": "bdfb97f83e4db7a13e8faab26fb1958d1b80cc64366501af78a0aee151696eb8" - }, - "@std/io@0.221.0": { - "integrity": "faf7f8700d46ab527fa05cc6167f4b97701a06c413024431c6b4d207caa010da", - "dependencies": [ - "jsr:@std/assert@0.221", - "jsr:@std/bytes" - ] - }, - "@std/path@1.1.1": { - "integrity": "fe00026bd3a7e6a27f73709b83c607798be40e20c81dde655ce34052fd82ec76", - "dependencies": [ - "jsr:@std/internal@^1.0.9" - ] - }, - "@std/streams@0.221.0": { - "integrity": "47f2f74634b47449277c0ee79fe878da4424b66bd8975c032e3afdca88986e61", - "dependencies": [ - "jsr:@std/io" - ] - }, - "@std/toml@1.0.8": { - "integrity": "eb8ae76b4cc1c6c13f2a91123675823adbec2380de75cd3748c628960d952164", - "dependencies": [ - "jsr:@std/collections" - ] - }, - "@std/yaml@1.0.8": { - "integrity": "90b8aab62995e929fa0ea5f4151c287275b63e321ac375c35ff7406ca60c169d" - } - }, - "npm": { - "@isaacs/balanced-match@4.0.1": { - "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==" - }, - "@isaacs/brace-expansion@5.0.0": { - "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", - "dependencies": [ - "@isaacs/balanced-match" - ] - }, - "@isaacs/cliui@8.0.2": { - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dependencies": [ - "string-width@5.1.2", - "string-width-cjs@npm:string-width@4.2.3", - "strip-ansi@7.1.0", - "strip-ansi-cjs@npm:strip-ansi@6.0.1", - "wrap-ansi@8.1.0", - "wrap-ansi-cjs@npm:wrap-ansi@7.0.0" - ] - }, - "ansi-regex@5.0.1": { - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" - }, - "ansi-regex@6.1.0": { - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==" - }, - "ansi-styles@4.3.0": { - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": [ - "color-convert" - ] - }, - "ansi-styles@6.2.1": { - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==" - }, - "color-convert@2.0.1": { - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": [ - "color-name" - ] - }, - "color-name@1.1.4": { - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "cross-spawn@7.0.6": { - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dependencies": [ - "path-key", - "shebang-command", - "which" - ] - }, - "eastasianwidth@0.2.0": { - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" - }, - "emoji-regex@8.0.0": { - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "emoji-regex@9.2.2": { - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" - }, - "foreground-child@3.3.1": { - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dependencies": [ - "cross-spawn", - "signal-exit" - ] - }, - "glob@11.0.3": { - "integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==", - "dependencies": [ - "foreground-child", - "jackspeak", - "minimatch", - "minipass", - "package-json-from-dist", - "path-scurry" - ], - "bin": true - }, - "is-fullwidth-code-point@3.0.0": { - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - }, - "isexe@2.0.0": { - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" - }, - "jackspeak@4.1.1": { - "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", - "dependencies": [ - "@isaacs/cliui" - ] - }, - "lru-cache@11.1.0": { - "integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==" - }, - "minimatch@10.0.3": { - "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", - "dependencies": [ - "@isaacs/brace-expansion" - ] - }, - "minipass@7.1.2": { - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==" - }, - "package-json-from-dist@1.0.1": { - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==" - }, - "path-key@3.1.1": { - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" - }, - "path-scurry@2.0.0": { - "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", - "dependencies": [ - "lru-cache", - "minipass" - ] - }, - "shebang-command@2.0.0": { - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dependencies": [ - "shebang-regex" - ] - }, - "shebang-regex@3.0.0": { - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" - }, - "signal-exit@4.1.0": { - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==" - }, - "string-width@4.2.3": { - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": [ - "emoji-regex@8.0.0", - "is-fullwidth-code-point", - "strip-ansi@6.0.1" - ] - }, - "string-width@5.1.2": { - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dependencies": [ - "eastasianwidth", - "emoji-regex@9.2.2", - "strip-ansi@7.1.0" - ] - }, - "strip-ansi@6.0.1": { - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": [ - "ansi-regex@5.0.1" - ] - }, - "strip-ansi@7.1.0": { - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dependencies": [ - "ansi-regex@6.1.0" - ] - }, - "which@2.0.2": { - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dependencies": [ - "isexe" - ], - "bin": true - }, - "wrap-ansi@7.0.0": { - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dependencies": [ - "ansi-styles@4.3.0", - "string-width@4.2.3", - "strip-ansi@6.0.1" - ] - }, - "wrap-ansi@8.1.0": { - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dependencies": [ - "ansi-styles@6.2.1", - "string-width@5.1.2", - "strip-ansi@7.1.0" - ] - } - }, - "workspace": { - "dependencies": [ - "jsr:@david/dax@0.42", - "jsr:@std/assert@^1.0.8", - "jsr:@std/cli@^1.0.8", - "jsr:@std/fs@^1.0.5", - "jsr:@std/path@^1.0.8", - "jsr:@std/toml@^1.0.1", - "jsr:@std/yaml@^1.0.5", - "npm:dedent@^1.5.3", - "npm:glob@11", - "npm:type-fest@^4.32.0" - ] - } -} diff --git a/scripts/openapi/gen_rust.ts b/scripts/openapi/gen_rust.ts index fb449156bd..7237b06e75 100755 --- a/scripts/openapi/gen_rust.ts +++ b/scripts/openapi/gen_rust.ts @@ -1,41 +1,59 @@ -#!/usr/bin/env -S deno run -A +#!/usr/bin/env tsx -import { assert } from "@std/assert"; +import { spawn } from 'node:child_process'; +import { rm, readFile, writeFile } from 'node:fs/promises'; +import { promisify } from 'node:util'; -const FERN_GROUP = Deno.env.get("FERN_GROUP"); -if (!FERN_GROUP) throw "Missing FERN_GROUP"; +const FERN_GROUP = process.env.FERN_GROUP; +if (!FERN_GROUP) throw new Error("Missing FERN_GROUP"); const OPENAPI_PATH = `engine/artifacts/openapi.json`; const GEN_PATH_RUST = `engine/sdks/rust/api-${FERN_GROUP}/rust`; +function runCommand(command: string, args: string[]): Promise { + return new Promise((resolve, reject) => { + const child = spawn(command, args, { + stdio: 'inherit', + cwd: process.cwd(), + }); + + child.on('exit', (code) => { + if (code === 0) { + resolve(); + } else { + reject(new Error(`${command} exited with code ${code}`)); + } + }); + + child.on('error', reject); + }); +} + async function generateRustSdk() { console.log("Running OpenAPI generator"); // Delete existing directories - await Deno.remove(GEN_PATH_RUST, { recursive: true }).catch(() => { }); - - const dockerCmd = new Deno.Command("docker", { - args: [ - "run", - "--rm", - `-u=${Deno.uid()}:${Deno.gid()}`, - `-v=${Deno.cwd()}:/data`, - "openapitools/openapi-generator-cli:v7.14.0", - "generate", - "-i", - `/data/${OPENAPI_PATH}`, - "--additional-properties=removeEnumValuePrefix=false", - "-g", - "rust", - "-o", - `/data/${GEN_PATH_RUST}`, - "-p", - `packageName=rivet-api-${FERN_GROUP}`, - ], - stdout: "inherit", - stderr: "inherit", - }); - const dockerResult = await dockerCmd.spawn().status; - assert(dockerResult.success, "Docker command failed"); + await rm(GEN_PATH_RUST, { recursive: true, force: true }); + + const uid = process.getuid?.() ?? 1000; + const gid = process.getgid?.() ?? 1000; + + await runCommand("docker", [ + "run", + "--rm", + `-u=${uid}:${gid}`, + `-v=${process.cwd()}:/data`, + "openapitools/openapi-generator-cli:v7.14.0", + "generate", + "-i", + `/data/${OPENAPI_PATH}`, + "--additional-properties=removeEnumValuePrefix=false", + "-g", + "rust", + "-o", + `/data/${GEN_PATH_RUST}`, + "-p", + `packageName=rivet-api-${FERN_GROUP}`, + ]); } async function fixOpenApiBugs() { @@ -68,11 +86,11 @@ async function fixOpenApiBugs() { for (const [file, replacements] of Object.entries(files)) { const filePath = `${GEN_PATH_RUST}/src/apis/${file}`; - let content; + let content: string; try { - content = await Deno.readTextFile(filePath); - } catch (error) { - if (error instanceof Deno.errors.NotFound) { + content = await readFile(filePath, 'utf-8'); + } catch (error: any) { + if (error.code === 'ENOENT') { console.warn(`File not found: ${filePath}`); continue; } else { @@ -83,19 +101,19 @@ async function fixOpenApiBugs() { for (const [from, to] of replacements) { content = content.replace(from, to); } - await Deno.writeTextFile(filePath, content); + await writeFile(filePath, content, 'utf-8'); } } async function modifyDependencies() { // Remove reqwest's dependency on OpenSSL in favor of Rustls const cargoTomlPath = `${GEN_PATH_RUST}/Cargo.toml`; - let cargoToml = await Deno.readTextFile(cargoTomlPath); + let cargoToml = await readFile(cargoTomlPath, 'utf-8'); cargoToml = cargoToml.replace( /\[dependencies\.reqwest\]/, "[dependencies.reqwest]\ndefault-features = false", ); - await Deno.writeTextFile(cargoTomlPath, cargoToml); + await writeFile(cargoTomlPath, cargoToml, 'utf-8'); } async function applyErrorPatch() { @@ -104,17 +122,12 @@ async function applyErrorPatch() { // Improve the display printing of errors const modRsPath = `${GEN_PATH_RUST}/src/apis/mod.rs`; const patchFilePath = "./scripts/openapi/error.patch"; - const patchProcess = new Deno.Command("patch", { - args: [modRsPath, patchFilePath], - stdout: "inherit", - stderr: "inherit", - }); - const { success } = await patchProcess.spawn().status; - assert(success, "Failed to apply patch"); + + await runCommand("patch", [modRsPath, patchFilePath]); } async function formatSdk() { - await new Deno.Command("cargo", { args: ["fmt"] }).output(); + await runCommand("cargo", ["fmt"]); } async function main() { @@ -128,4 +141,7 @@ async function main() { console.log("Done"); } -main(); +main().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/scripts/run/k8s/engine.sh b/scripts/run/k8s/engine.sh index b726cfa856..b84ba64465 100755 --- a/scripts/run/k8s/engine.sh +++ b/scripts/run/k8s/engine.sh @@ -54,16 +54,11 @@ kubectl apply -f 02-engine-configmap.yaml kubectl apply -f 03-rivet-engine-deployment.yaml kubectl apply -f 04-rivet-engine-service.yaml kubectl apply -f 05-rivet-engine-hpa.yaml -kubectl apply -f 06-rivet-engine-singleton-deployment.yaml # Wait for engine to be ready echo "Waiting for engine to be ready..." kubectl -n "${NAMESPACE}" wait --for=condition=ready pod -l app=rivet-engine --timeout=300s -# Wait for singleton to be ready -echo "Waiting for singleton to be ready..." -kubectl -n "${NAMESPACE}" wait --for=condition=ready pod -l app=rivet-engine-singleton --timeout=300s - echo "" echo "Deployment complete." echo ""