diff --git a/Cargo.lock b/Cargo.lock index 4825845..6c0cb6e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -492,7 +492,9 @@ dependencies = [ "anyhow", "clap", "comet", + "proc-macro2", "quote", + "serde", "serde_json", "syn", "tempfile", diff --git a/comet-cli/Cargo.toml b/comet-cli/Cargo.toml index a01a688..309e8ad 100644 --- a/comet-cli/Cargo.toml +++ b/comet-cli/Cargo.toml @@ -13,9 +13,11 @@ path = "src/main.rs" [dependencies] clap = { version = "4", features = ["derive"] } anyhow = "1" -syn = { version = "2", features = ["full"] } +syn = { version = "2", features = ["full", "visit"] } quote = "1" +proc-macro2 = "1" toml = "0.8" +serde = { version = "1", features = ["derive"] } serde_json = "1" tempfile = "3" comet = { path = "..", default-features = false, features = ["nebula-schema"] } diff --git a/comet-cli/src/cli.rs b/comet-cli/src/cli.rs index cdf7ab4..a72e40b 100644 --- a/comet-cli/src/cli.rs +++ b/comet-cli/src/cli.rs @@ -1,6 +1,6 @@ use std::path::PathBuf; -use clap::{Args, Parser, Subcommand}; +use clap::{Args, Parser, Subcommand, ValueEnum}; #[derive(Parser)] #[command( @@ -29,6 +29,9 @@ pub enum Command { /// Inspect Nebula RLS coverage. #[command(subcommand)] Rls(RlsCommand), + /// Inspect routes and generate typed RPC contracts/clients. + #[command(subcommand)] + Rpc(RpcCommand), /// Run the project's test/release gate. #[command(subcommand)] Test(TestCommand), @@ -46,6 +49,14 @@ pub enum RlsCommand { Status(RlsStatusArgs), } +#[derive(Subcommand)] +pub enum RpcCommand { + /// Emit the discovered RPC manifest as JSON. + Manifest(RpcManifestArgs), + /// Generate a typed RPC client from discovered routes. + Generate(RpcGenerateArgs), +} + #[derive(Args)] pub struct NewArgs { /// Project name; also used as the crate name and D1 database name. @@ -178,6 +189,39 @@ pub struct RlsStatusArgs { pub custom_predicate_rules: Vec, } +#[derive(Args)] +pub struct RpcManifestArgs { + /// Project directory to inspect. Defaults to the current directory. + #[arg(long)] + pub path: Option, + + /// File to write. Defaults to stdout. + #[arg(long)] + pub out: Option, +} + +#[derive(Args)] +pub struct RpcGenerateArgs { + /// Project directory to inspect. Defaults to the current directory. + #[arg(long)] + pub path: Option, + + /// Client language to generate. + #[arg(long)] + pub lang: RpcLanguage, + + /// File to write. Defaults to stdout. + #[arg(long)] + pub out: Option, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, ValueEnum)] +pub enum RpcLanguage { + Ts, + Dart, + Rust, +} + #[derive(Subcommand)] pub enum TestCommand { /// `cargo fmt --check` + `cargo test --lib`. @@ -196,3 +240,26 @@ pub struct TestArgs { #[arg(long)] pub path: Option, } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn rpc_generate_requires_supported_language() { + let result = Cli::try_parse_from([ + "comet", + "rpc", + "generate", + "--lang", + "go", + "--out", + "client.go", + ]); + let Err(error) = result else { + panic!("expected invalid rpc language to fail"); + }; + + assert_eq!(error.kind(), clap::error::ErrorKind::InvalidValue); + } +} diff --git a/comet-cli/src/commands/mod.rs b/comet-cli/src/commands/mod.rs index dfefa87..6a93c3c 100644 --- a/comet-cli/src/commands/mod.rs +++ b/comet-cli/src/commands/mod.rs @@ -3,4 +3,5 @@ pub mod generate; pub mod migrate; pub mod new; pub mod rls; +pub mod rpc; pub mod test; diff --git a/comet-cli/src/commands/rpc.rs b/comet-cli/src/commands/rpc.rs new file mode 100644 index 0000000..ff4acb0 --- /dev/null +++ b/comet-cli/src/commands/rpc.rs @@ -0,0 +1,133 @@ +use std::fs; +use std::path::PathBuf; + +use anyhow::{Context, Result, bail}; + +use crate::cli::{RpcGenerateArgs, RpcLanguage, RpcManifestArgs}; +use crate::rpc; + +pub fn manifest(args: RpcManifestArgs) -> Result<()> { + let project_dir = args.path.unwrap_or_else(|| PathBuf::from(".")); + let manifest = rpc::discover_manifest(&project_dir)?; + if manifest.routes.is_empty() { + bail!( + "no Rocket route attributes found under {}; RPC manifest generation needs at least one #[get]/#[post]/#[put]/#[delete]/#[patch] route", + project_dir.join("src").display() + ); + } + let json = serde_json::to_string_pretty(&manifest).context("serializing RPC manifest")?; + + match args.out { + Some(path) => { + if let Some(parent) = path.parent() + && !parent.as_os_str().is_empty() + { + fs::create_dir_all(parent) + .with_context(|| format!("creating directory {}", parent.display()))?; + } + fs::write(&path, json).with_context(|| format!("writing {}", path.display()))?; + } + None => println!("{json}"), + } + + Ok(()) +} + +pub fn generate(args: RpcGenerateArgs) -> Result<()> { + let project_dir = args.path.unwrap_or_else(|| PathBuf::from(".")); + let manifest = rpc::discover_manifest(&project_dir)?; + if !manifest + .routes + .iter() + .any(|route| route.support == rpc::RpcRouteSupport::Json) + { + bail!( + "no JSON RPC routes found under {}; client generation only supports routes with Json inputs or outputs", + project_dir.join("src").display() + ); + } + let output = match args.lang { + RpcLanguage::Ts => { + let types = rpc::discover_typescript_types(&project_dir, &manifest)?; + rpc::generate_typescript_client_with_types(&manifest, &types) + } + RpcLanguage::Dart => { + let types = rpc::discover_typescript_types(&project_dir, &manifest)?; + rpc::generate_dart_client_with_types(&manifest, &types) + } + RpcLanguage::Rust => { + let types = rpc::discover_typescript_types(&project_dir, &manifest)?; + rpc::generate_rust_client_with_types(&manifest, &types) + } + }; + + match args.out { + Some(path) => write_output(&path, output), + None => { + println!("{output}"); + Ok(()) + } + } +} + +fn write_output(path: &PathBuf, output: String) -> Result<()> { + if let Some(parent) = path.parent() + && !parent.as_os_str().is_empty() + { + fs::create_dir_all(parent) + .with_context(|| format!("creating directory {}", parent.display()))?; + } + + fs::write(path, output).with_context(|| format!("writing {}", path.display())) +} + +#[cfg(test)] +mod tests { + use std::fs; + + use super::*; + use crate::cli::{RpcGenerateArgs, RpcLanguage}; + + #[test] + fn manifest_errors_when_no_routes_are_found() { + let dir = tempfile::tempdir().unwrap(); + fs::create_dir_all(dir.path().join("src")).unwrap(); + + let error = manifest(RpcManifestArgs { + path: Some(dir.path().to_path_buf()), + out: Some(dir.path().join("rpc.json")), + }) + .unwrap_err(); + + assert!( + error + .to_string() + .contains("no Rocket route attributes found") + ); + } + + #[test] + fn generate_errors_when_no_json_routes_are_found() { + let dir = tempfile::tempdir().unwrap(); + fs::create_dir_all(dir.path().join("src")).unwrap(); + fs::write( + dir.path().join("src/routes.rs"), + r#" + #[get("/")] + pub fn index() -> &'static str { + "ok" + } + "#, + ) + .unwrap(); + + let error = generate(RpcGenerateArgs { + path: Some(dir.path().to_path_buf()), + lang: RpcLanguage::Ts, + out: Some(dir.path().join("client.ts")), + }) + .unwrap_err(); + + assert!(error.to_string().contains("no JSON RPC routes found")); + } +} diff --git a/comet-cli/src/main.rs b/comet-cli/src/main.rs index 7f1e259..9ee4f08 100644 --- a/comet-cli/src/main.rs +++ b/comet-cli/src/main.rs @@ -4,12 +4,15 @@ mod commands; mod discover; mod entity_introspect; mod fieldspec; +mod rpc; mod rustfile; mod schema_dump; mod snapshot; use clap::Parser; -use cli::{AuthCommand, Command, GenerateCommand, MigrateCommand, RlsCommand, TestCommand}; +use cli::{ + AuthCommand, Command, GenerateCommand, MigrateCommand, RlsCommand, RpcCommand, TestCommand, +}; fn main() -> anyhow::Result<()> { let cli = cli::Cli::parse(); @@ -23,6 +26,8 @@ fn main() -> anyhow::Result<()> { Command::Migrate(MigrateCommand::Status(args)) => commands::migrate::status(args), Command::Auth(AuthCommand::Init(args)) => commands::auth::init(args), Command::Rls(RlsCommand::Status(args)) => commands::rls::status(args), + Command::Rpc(RpcCommand::Manifest(args)) => commands::rpc::manifest(args), + Command::Rpc(RpcCommand::Generate(args)) => commands::rpc::generate(args), Command::Test(TestCommand::Unit(args)) => commands::test::unit(args), Command::Test(TestCommand::Integration(args)) => commands::test::integration(args), Command::Test(TestCommand::Perf(args)) => commands::test::perf(args), diff --git a/comet-cli/src/rpc.rs b/comet-cli/src/rpc.rs new file mode 100644 index 0000000..d866628 --- /dev/null +++ b/comet-cli/src/rpc.rs @@ -0,0 +1,3309 @@ +use std::collections::BTreeMap; +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::Command; + +use anyhow::{Context, Result, bail}; +use quote::ToTokens; +use serde::{Deserialize, Serialize}; +use syn::parse::{Parse, ParseStream}; +use syn::punctuated::Punctuated; +use syn::visit::{self, Visit}; +use syn::{ + Attribute, Expr, ExprMethodCall, Field, Fields, FnArg, GenericArgument, Item, ItemFn, Lit, + LitStr, Pat, PathArguments, ReturnType, Token, Type, Visibility, +}; +use toml::Value; + +const MANIFEST_VERSION: u32 = 1; + +#[derive(Debug, Serialize, PartialEq, Eq)] +pub struct RpcManifest { + pub version: u32, + pub routes: Vec, +} + +#[derive(Debug, Serialize, PartialEq, Eq)] +pub struct RpcRoute { + pub name: String, + pub module_path: Vec, + pub source: String, + pub method: String, + pub path: String, + pub data_param: Option, + pub path_params: Vec, + pub query_params: Vec, + pub body: Option, + pub response: Option, + pub error: Option, + pub auth: RpcAuth, + pub support: RpcRouteSupport, + pub warnings: Vec, +} + +#[derive(Debug, Serialize, PartialEq, Eq)] +pub struct RpcParam { + pub name: String, + pub rust_type: String, + pub variadic: bool, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TsModel { + pub name: String, + pub fields: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TsModelField { + pub name: String, + pub rust_type: String, + pub ts_type: String, + pub optional: bool, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TsEnum { + pub name: String, + pub variants: Vec, +} + +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct TsTypes { + pub models: BTreeMap, + pub enums: BTreeMap, +} + +#[derive(Debug, Serialize, PartialEq, Eq)] +#[serde(tag = "kind", rename_all = "snake_case")] +pub enum RpcAuth { + None, + Optional, + Required, + Authorized { + policy: Option, + roles: Vec, + permissions: Vec, + scopes: Vec, + resource: Option, + mode: AuthMode, + }, +} + +#[derive(Debug, Clone, Serialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum AuthMode { + All, + Any, +} + +#[derive(Debug, Serialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum RpcRouteSupport { + Json, + Raw, + Unsupported, +} + +#[derive(Debug)] +struct RocketRouteAttr { + method: String, + path: String, + data_param: Option, +} + +struct RocketRouteArgs { + path: LitStr, + data_param: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct ResultAlias { + name: String, + error_type: String, +} + +impl Parse for RocketRouteArgs { + fn parse(input: ParseStream<'_>) -> syn::Result { + let path = input.parse::()?; + let mut data_param = None; + + while input.peek(Token![,]) { + input.parse::()?; + if input.is_empty() { + break; + } + + let key = input.parse::()?; + input.parse::()?; + let value = input.parse::()?; + if key == "data" { + data_param = Some(strip_angle_binding(&value.value()).to_owned()); + } + } + + Ok(Self { path, data_param }) + } +} + +pub fn discover_manifest(project_dir: &Path) -> Result { + let src_dir = project_dir.join("src"); + let mut routes = Vec::new(); + let result_aliases = discover_project_result_aliases(&src_dir)?; + visit_dir(&src_dir, &[], &result_aliases, &mut routes)?; + let mount_prefixes = discover_mount_prefixes(&src_dir)?; + apply_mount_prefixes(&mount_prefixes, &mut routes); + resolve_authorization_policies(project_dir, &mut routes)?; + routes.sort_by(|a, b| { + a.module_path + .cmp(&b.module_path) + .then_with(|| a.name.cmp(&b.name)) + .then_with(|| a.method.cmp(&b.method)) + }); + + Ok(RpcManifest { + version: MANIFEST_VERSION, + routes, + }) +} + +pub fn generate_typescript_client_with_types(manifest: &RpcManifest, types: &TsTypes) -> String { + let json_routes = manifest + .routes + .iter() + .filter(|route| route.support == RpcRouteSupport::Json) + .collect::>(); + let declarations = typescript_type_declarations(&json_routes, types); + let methods = json_routes + .iter() + .map(|route| render_typescript_method(route)) + .collect::>() + .join("\n\n"); + + let declarations = if declarations.is_empty() { + String::new() + } else { + format!("{}\n\n", declarations.join("\n\n")) + }; + + format!( + "/* Generated by comet rpc generate. Do not edit by hand. */\n\n\ + export type TokenProvider = () => string | null | undefined | Promise;\n\n\ + {declarations}\ + export class CometRpcError extends Error {{\n\ + \x20 readonly status: number;\n\ + \x20 readonly body: unknown;\n\n\ + \x20 constructor(status: number, body: unknown) {{\n\ + \x20 super(`Comet RPC request failed with status ${{status}}`);\n\ + \x20 this.name = \"CometRpcError\";\n\ + \x20 this.status = status;\n\ + \x20 this.body = body;\n\ + \x20 }}\n\ + }}\n\n\ + export class CometClient {{\n\ + \x20 constructor(\n\ + \x20 private readonly baseUrl: string,\n\ + \x20 private readonly tokenProvider?: TokenProvider,\n\ + \x20 ) {{}}\n\n\ + {methods}\n\n\ + \x20 private async request(method: string, path: string, body: unknown, auth: boolean): Promise {{\n\ + \x20 const headers: Record = {{ accept: \"application/json\" }};\n\ + \x20 let requestBody: string | undefined;\n\ + \x20 if (body !== undefined) {{\n\ + \x20 headers[\"content-type\"] = \"application/json\";\n\ + \x20 requestBody = JSON.stringify(body);\n\ + \x20 }}\n\ + \x20 if (auth && this.tokenProvider) {{\n\ + \x20 const token = await this.tokenProvider();\n\ + \x20 if (token) headers.authorization = `Bearer ${{token}}`;\n\ + \x20 }}\n\n\ + \x20 const response = await fetch(`${{this.baseUrl.replace(/\\/$/, \"\")}}${{path}}`, {{\n\ + \x20 method,\n\ + \x20 headers,\n\ + \x20 body: requestBody,\n\ + \x20 }});\n\ + \x20 const text = await response.text();\n\ + \x20 const payload = parseJsonResponse(text, response.ok);\n\ + \x20 if (!response.ok) throw new CometRpcError(response.status, payload);\n\ + \x20 return payload as T;\n\ + \x20 }}\n\ + }}\n\n\ + function parseJsonResponse(text: string, ok: boolean): unknown {{\n\ + \x20 if (!text) return undefined;\n\ + \x20 try {{\n\ + \x20 return JSON.parse(text);\n\ + \x20 }} catch {{\n\ + \x20 if (ok) throw new SyntaxError(\"Comet RPC response was not valid JSON\");\n\ + \x20 return text;\n\ + \x20 }}\n\ + }}\n\n\ + function encodePathValue(value: string | number | boolean | Array): string {{\n\ + \x20 if (Array.isArray(value)) return value.map((item) => encodeURIComponent(String(item))).join(\"/\");\n\ + \x20 return encodeURIComponent(String(value));\n\ + }}\n" + ) +} + +pub fn generate_dart_client_with_types(manifest: &RpcManifest, types: &TsTypes) -> String { + let json_routes = manifest + .routes + .iter() + .filter(|route| route.support == RpcRouteSupport::Json) + .collect::>(); + let declarations = dart_type_declarations(&json_routes, types); + let methods = json_routes + .iter() + .map(|route| render_dart_method(route)) + .collect::>() + .join("\n\n"); + + let declarations = if declarations.is_empty() { + String::new() + } else { + format!("{}\n\n", declarations.join("\n\n")) + }; + + format!( + "// Generated by comet rpc generate. Do not edit by hand.\n\n\ + import 'dart:async';\n\ + import 'dart:convert';\n\n\ + import 'package:http/http.dart' as http;\n\n\ + typedef TokenProvider = FutureOr Function();\n\n\ + {declarations}\ + class CometRpcException implements Exception {{\n\ + \x20 final int status;\n\ + \x20 final Object? body;\n\n\ + \x20 CometRpcException(this.status, this.body);\n\n\ + \x20 @override\n\ + \x20 String toString() => 'CometRpcException(status: $status)';\n\ + }}\n\n\ + class CometClient {{\n\ + \x20 final String baseUrl;\n\ + \x20 final TokenProvider? tokenProvider;\n\ + \x20 final http.Client _http;\n\n\ + \x20 CometClient(this.baseUrl, {{this.tokenProvider, http.Client? httpClient}})\n\ + \x20 : _http = httpClient ?? http.Client();\n\n\ + {methods}\n\n\ + \x20 Future _request(String method, String path, Object? body, bool auth, T Function(Object?) decode) async {{\n\ + \x20 final headers = {{'accept': 'application/json'}};\n\ + \x20 String? requestBody;\n\ + \x20 if (body != null) {{\n\ + \x20 headers['content-type'] = 'application/json';\n\ + \x20 requestBody = jsonEncode(_toJson(body));\n\ + \x20 }}\n\ + \x20 if (auth && tokenProvider != null) {{\n\ + \x20 final token = await tokenProvider!();\n\ + \x20 if (token != null && token.isNotEmpty) headers['authorization'] = 'Bearer $token';\n\ + \x20 }}\n\n\ + \x20 final uri = Uri.parse('${{baseUrl.replaceFirst(RegExp(r'/\\$'), '')}}$path');\n\ + \x20 final response = await _http.send(http.Request(method, uri)\n\ + \x20 ..headers.addAll(headers)\n\ + \x20 ..body = requestBody ?? '');\n\ + \x20 final text = await response.stream.bytesToString();\n\ + \x20 final ok = response.statusCode >= 200 && response.statusCode < 300;\n\ + \x20 final payload = _decodeJsonResponse(text, ok);\n\ + \x20 if (!ok) {{\n\ + \x20 throw CometRpcException(response.statusCode, payload);\n\ + \x20 }}\n\ + \x20 return decode(payload);\n\ + \x20 }}\n\ + }}\n\n\ + Object? _decodeJsonResponse(String text, bool ok) {{\n\ + \x20 if (text.isEmpty) return null;\n\ + \x20 try {{\n\ + \x20 return jsonDecode(text);\n\ + \x20 }} on FormatException {{\n\ + \x20 if (ok) rethrow;\n\ + \x20 return text;\n\ + \x20 }}\n\ + }}\n\n\ + Object? _toJson(Object? value) {{\n\ + \x20 if (value == null) return null;\n\ + \x20 if (value is List) return value.map(_toJson).toList();\n\ + \x20 if (value is Map) return value.map((key, value) => MapEntry(key, _toJson(value)));\n\ + \x20 try {{\n\ + \x20 return (value as dynamic).toJson();\n\ + \x20 }} catch (_) {{\n\ + \x20 return value;\n\ + \x20 }}\n\ + }}\n\n\ + String _encodePathValue(Object value) {{\n\ + \x20 if (value is List) return value.map((item) => Uri.encodeComponent('$item')).join('/');\n\ + \x20 return Uri.encodeComponent('$value');\n\ + }}\n" + ) +} + +pub fn generate_rust_client_with_types(manifest: &RpcManifest, types: &TsTypes) -> String { + let json_routes = manifest + .routes + .iter() + .filter(|route| route.support == RpcRouteSupport::Json) + .collect::>(); + let declarations = rust_type_declarations(&json_routes, types); + let methods = json_routes + .iter() + .map(|route| render_rust_method(route)) + .collect::>() + .join("\n\n"); + + let declarations = if declarations.is_empty() { + String::new() + } else { + format!("{}\n\n", declarations.join("\n\n")) + }; + + format!( + "// Generated by comet rpc generate. Do not edit by hand.\n\n\ + use serde::{{Deserialize, Serialize}};\n\n\ + {declarations}\ + #[derive(Debug, thiserror::Error)]\n\ + pub enum CometRpcError {{\n\ + \x20 #[error(\"HTTP client error: {{0}}\")] \n\ + \x20 Http(#[from] reqwest::Error),\n\ + \x20 #[error(\"JSON decode error: {{0}}\")] \n\ + \x20 Json(#[from] serde_json::Error),\n\ + \x20 #[error(\"Comet RPC request failed with status {{status}}\")]\n\ + \x20 Status {{ status: reqwest::StatusCode, body: String }},\n\ + }}\n\n\ + #[derive(Clone)]\n\ + pub struct CometClient {{\n\ + \x20 base_url: String,\n\ + \x20 token: Option,\n\ + \x20 http: reqwest::Client,\n\ + }}\n\n\ + impl CometClient {{\n\ + \x20 pub fn new(base_url: impl Into) -> Self {{\n\ + \x20 Self {{ base_url: base_url.into(), token: None, http: reqwest::Client::new() }}\n\ + \x20 }}\n\n\ + \x20 pub fn with_bearer_token(mut self, token: impl Into) -> Self {{\n\ + \x20 self.token = Some(token.into());\n\ + \x20 self\n\ + \x20 }}\n\n\ + \x20 pub fn with_http_client(mut self, http: reqwest::Client) -> Self {{\n\ + \x20 self.http = http;\n\ + \x20 self\n\ + \x20 }}\n\n\ + {methods}\n\n\ + \x20 async fn request(\n\ + \x20 &self,\n\ + \x20 method: reqwest::Method,\n\ + \x20 path: String,\n\ + \x20 body: Option<&B>,\n\ + \x20 auth: bool,\n\ + \x20 ) -> Result {{\n\ + \x20 let url = format!(\"{{}}{{}}\", self.base_url.trim_end_matches('/'), path);\n\ + \x20 let mut request = self.http.request(method, url).header(\"accept\", \"application/json\");\n\ + \x20 if let Some(body) = body {{\n\ + \x20 request = request.json(body);\n\ + \x20 }}\n\ + \x20 if auth {{\n\ + \x20 if let Some(token) = &self.token {{\n\ + \x20 request = request.bearer_auth(token);\n\ + \x20 }}\n\ + \x20 }}\n\ + \x20 let response = request.send().await?;\n\ + \x20 let status = response.status();\n\ + \x20 let body = response.bytes().await?;\n\ + \x20 if !status.is_success() {{\n\ + \x20 return Err(CometRpcError::Status {{\n\ + \x20 status,\n\ + \x20 body: String::from_utf8_lossy(&body).into_owned(),\n\ + \x20 }});\n\ + \x20 }}\n\ + \x20 if body.is_empty() {{\n\ + \x20 return Ok(serde_json::from_str(\"null\")?);\n\ + \x20 }}\n\ + \x20 Ok(serde_json::from_slice::(&body)?)\n\ + \x20 }}\n\ + }}\n\n\ + fn encode_path_value(value: impl ToString) -> String {{\n\ + \x20 utf8_percent_encode(&value.to_string(), NON_ALPHANUMERIC).to_string()\n\ + }}\n\n\ + use percent_encoding::{{utf8_percent_encode, NON_ALPHANUMERIC}};\n" + ) +} + +pub fn discover_typescript_types(project_dir: &Path, manifest: &RpcManifest) -> Result { + let mut wanted = typescript_type_names_for_manifest(manifest); + if wanted.is_empty() { + return Ok(TsTypes::default()); + } + + let src_dir = project_dir.join("src"); + let mut types = TsTypes::default(); + let mut scanned = std::collections::BTreeSet::new(); + + loop { + let pending = wanted + .difference(&scanned) + .cloned() + .collect::>(); + if pending.is_empty() { + break; + } + + visit_model_dir(&src_dir, &pending, &mut types)?; + scanned.extend(pending); + + for model in types.models.values() { + for field in &model.fields { + collect_custom_type_names(&field.rust_type, &mut wanted); + } + } + } + + Ok(types) +} + +fn visit_model_dir( + dir: &Path, + wanted: &std::collections::BTreeSet, + types: &mut TsTypes, +) -> Result<()> { + let mut dir_entries = fs::read_dir(dir) + .with_context(|| format!("reading directory {}", dir.display()))? + .collect::>>() + .with_context(|| format!("reading directory {}", dir.display()))?; + dir_entries.sort_by_key(|entry| entry.file_name()); + + for entry in dir_entries { + let path = entry.path(); + let file_type = entry + .file_type() + .with_context(|| format!("reading file type of {}", path.display()))?; + + if file_type.is_dir() { + visit_model_dir(&path, wanted, types)?; + } else if path.extension().and_then(|ext| ext.to_str()) == Some("rs") { + visit_model_file(&path, wanted, types)?; + } + } + + Ok(()) +} + +fn visit_model_file( + path: &Path, + wanted: &std::collections::BTreeSet, + types: &mut TsTypes, +) -> Result<()> { + let source = fs::read_to_string(path).with_context(|| format!("reading {}", path.display()))?; + let file = syn::parse_file(&source).with_context(|| format!("parsing {}", path.display()))?; + + for item in &file.items { + match item { + Item::Struct(item_struct) => { + let name = item_struct.ident.to_string(); + if wanted.contains(&name) + && let Some(model) = ts_model_from_struct(item_struct) + { + types.models.insert(name, model); + } + } + Item::Enum(item_enum) => { + let name = item_enum.ident.to_string(); + if wanted.contains(&name) + && let Some(ts_enum) = ts_enum_from_enum(item_enum) + { + types.enums.insert(name, ts_enum); + } + } + _ => {} + } + } + + Ok(()) +} + +fn ts_model_from_struct(item_struct: &syn::ItemStruct) -> Option { + let Fields::Named(fields) = &item_struct.fields else { + return None; + }; + let rename_all = serde_rename_all(&item_struct.attrs); + + let mut model_fields = Vec::new(); + for field in &fields.named { + let Some(model_field) = ts_model_field(field, rename_all.as_deref()) else { + continue; + }; + model_fields.push(model_field); + } + + Some(TsModel { + name: item_struct.ident.to_string(), + fields: model_fields, + }) +} + +fn ts_model_field(field: &Field, rename_all: Option<&str>) -> Option { + if !matches!(field.vis, Visibility::Public(_)) { + return None; + } + + let ident = field.ident.as_ref()?.to_string(); + if serde_skip(&field.attrs) { + return None; + } + let name = serde_rename(&field.attrs).unwrap_or_else(|| rename_field(&ident, rename_all)); + let rust_type = type_to_string(&field.ty); + let optional = generic_inner_for_last_segment(&rust_type, "Option").is_some(); + + Some(TsModelField { + name, + rust_type: rust_type.clone(), + ts_type: rust_type_to_typescript(&rust_type), + optional, + }) +} + +fn rename_field(name: &str, rename_all: Option<&str>) -> String { + match rename_all { + Some("snake_case") => to_snake_case(name), + Some("kebab-case") => to_snake_case(name).replace('_', "-"), + Some("SCREAMING_SNAKE_CASE") => to_snake_case(name).to_ascii_uppercase(), + Some("camelCase") => to_lower_camel_case(name), + Some("PascalCase") => to_pascal_case(name), + Some(_) | None => name.to_owned(), + } +} + +fn ts_enum_from_enum(item_enum: &syn::ItemEnum) -> Option { + let rename_all = serde_rename_all(&item_enum.attrs); + let mut variants = Vec::new(); + + for variant in &item_enum.variants { + if !matches!(variant.fields, Fields::Unit) { + return None; + } + let fallback = rename_variant(&variant.ident.to_string(), rename_all.as_deref()); + variants.push(serde_rename(&variant.attrs).unwrap_or(fallback)); + } + + Some(TsEnum { + name: item_enum.ident.to_string(), + variants, + }) +} + +fn serde_skip(attrs: &[Attribute]) -> bool { + attrs.iter().any(|attr| { + attr.path().is_ident("serde") + && attr + .parse_args_with(Punctuated::::parse_terminated) + .ok() + .is_some_and(|metas| { + metas.iter().any(|meta| match meta { + syn::Meta::Path(path) => path.is_ident("skip"), + syn::Meta::NameValue(name_value) => name_value.path.is_ident("skip"), + syn::Meta::List(list) => list.path.is_ident("skip"), + }) + }) + }) +} + +fn serde_rename(attrs: &[Attribute]) -> Option { + for attr in attrs { + if !attr.path().is_ident("serde") { + continue; + } + let metas = attr + .parse_args_with(Punctuated::::parse_terminated) + .ok()?; + for meta in metas { + let syn::Meta::NameValue(name_value) = meta else { + continue; + }; + if !name_value.path.is_ident("rename") { + continue; + } + let syn::Expr::Lit(expr_lit) = name_value.value else { + continue; + }; + let Lit::Str(value) = expr_lit.lit else { + continue; + }; + return Some(value.value()); + } + } + + None +} + +fn serde_rename_all(attrs: &[Attribute]) -> Option { + for attr in attrs { + if !attr.path().is_ident("serde") { + continue; + } + let metas = attr + .parse_args_with(Punctuated::::parse_terminated) + .ok()?; + for meta in metas { + let syn::Meta::NameValue(name_value) = meta else { + continue; + }; + if !name_value.path.is_ident("rename_all") { + continue; + } + let syn::Expr::Lit(expr_lit) = name_value.value else { + continue; + }; + let Lit::Str(value) = expr_lit.lit else { + continue; + }; + return Some(value.value()); + } + } + + None +} + +fn rename_variant(name: &str, rename_all: Option<&str>) -> String { + match rename_all { + Some("snake_case") => to_snake_case(name), + Some("kebab-case") => to_snake_case(name).replace('_', "-"), + Some("SCREAMING_SNAKE_CASE") => to_snake_case(name).to_ascii_uppercase(), + Some("camelCase") => to_lower_camel_case(name), + Some("PascalCase") | None => name.to_owned(), + Some(_) => name.to_owned(), + } +} + +fn to_snake_case(value: &str) -> String { + let mut output = String::new(); + let mut previous_was_lower_or_digit = false; + for ch in value.chars() { + if ch.is_ascii_uppercase() { + if previous_was_lower_or_digit && !output.is_empty() { + output.push('_'); + } + output.push(ch.to_ascii_lowercase()); + previous_was_lower_or_digit = false; + } else if ch == '-' || ch.is_whitespace() { + if !output.ends_with('_') && !output.is_empty() { + output.push('_'); + } + previous_was_lower_or_digit = false; + } else { + output.push(ch); + previous_was_lower_or_digit = ch.is_ascii_lowercase() || ch.is_ascii_digit(); + } + } + output +} + +fn discover_mount_prefixes(src_dir: &Path) -> Result>> { + let mut prefixes = BTreeMap::new(); + visit_mount_dir(src_dir, &mut prefixes)?; + Ok(prefixes) +} + +fn visit_mount_dir(dir: &Path, prefixes: &mut BTreeMap>) -> Result<()> { + let mut dir_entries = fs::read_dir(dir) + .with_context(|| format!("reading directory {}", dir.display()))? + .collect::>>() + .with_context(|| format!("reading directory {}", dir.display()))?; + dir_entries.sort_by_key(|entry| entry.file_name()); + + for entry in dir_entries { + let path = entry.path(); + let file_type = entry + .file_type() + .with_context(|| format!("reading file type of {}", path.display()))?; + + if file_type.is_dir() { + visit_mount_dir(&path, prefixes)?; + } else if path.extension().and_then(|ext| ext.to_str()) == Some("rs") { + visit_mount_file(&path, prefixes)?; + } + } + + Ok(()) +} + +fn visit_mount_file(path: &Path, prefixes: &mut BTreeMap>) -> Result<()> { + let source = fs::read_to_string(path).with_context(|| format!("reading {}", path.display()))?; + let file = syn::parse_file(&source).with_context(|| format!("parsing {}", path.display()))?; + let mut visitor = MountVisitor { prefixes }; + visitor.visit_file(&file); + Ok(()) +} + +struct MountVisitor<'a> { + prefixes: &'a mut BTreeMap>, +} + +impl<'ast> Visit<'ast> for MountVisitor<'_> { + fn visit_expr_method_call(&mut self, node: &'ast ExprMethodCall) { + if node.method == "mount" + && let Some(prefix) = mount_prefix(node) + { + for route_name in mounted_route_names(node) { + self.prefixes + .entry(route_name) + .or_default() + .push(prefix.clone()); + } + } + + visit::visit_expr_method_call(self, node); + } +} + +fn mount_prefix(node: &ExprMethodCall) -> Option { + let Expr::Lit(expr_lit) = node.args.first()? else { + return None; + }; + let Lit::Str(prefix) = &expr_lit.lit else { + return None; + }; + Some(prefix.value()) +} + +fn mounted_route_names(node: &ExprMethodCall) -> Vec { + let Some(Expr::Macro(expr_macro)) = node.args.iter().nth(1) else { + return Vec::new(); + }; + if !expr_macro + .mac + .path + .segments + .last() + .is_some_and(|segment| segment.ident == "routes") + { + return Vec::new(); + } + + expr_macro + .mac + .parse_body_with(Punctuated::::parse_terminated) + .ok() + .map(|paths| { + paths + .iter() + .filter_map(|path| path.segments.last()) + .map(|segment| segment.ident.to_string()) + .collect() + }) + .unwrap_or_default() +} + +fn apply_mount_prefixes(mount_prefixes: &BTreeMap>, routes: &mut [RpcRoute]) { + for route in routes { + let Some(prefixes) = mount_prefixes.get(&route.name) else { + continue; + }; + + let unique = prefixes.iter().collect::>(); + if unique.len() > 1 { + route.warnings.push(format!( + "route `{}` is mounted at multiple prefixes; leaving local path unchanged", + route.name + )); + continue; + } + + if let Some(prefix) = prefixes.first() { + route.path = join_mount_path(prefix, &route.path); + } + } +} + +fn join_mount_path(prefix: &str, route_path: &str) -> String { + let prefix = prefix.trim_end_matches('/'); + let route_path = route_path.trim_start_matches('/'); + + match (prefix.is_empty(), route_path.is_empty()) { + (true, true) => "/".to_owned(), + (true, false) => format!("/{route_path}"), + (false, true) => prefix.to_owned(), + (false, false) => format!("{prefix}/{route_path}"), + } +} + +fn render_typescript_method(route: &RpcRoute) -> String { + let method_name = to_lower_camel_case(&route.name); + let return_type = route + .response + .as_deref() + .map(rust_type_to_typescript) + .unwrap_or_else(|| "unknown".to_owned()); + let mut params = route + .path_params + .iter() + .chain(route.query_params.iter()) + .map(|param| { + let ts_type = if param.variadic { + "string | number | Array".to_owned() + } else { + rust_type_to_typescript(¶m.rust_type) + }; + format!("{}: {}", sanitize_ts_identifier(¶m.name), ts_type) + }) + .collect::>(); + + let body_arg = route.data_param.as_deref().unwrap_or("body"); + if let Some(body) = &route.body { + params.push(format!( + "{}: {}", + sanitize_ts_identifier(body_arg), + rust_type_to_typescript(body) + )); + } + + let body_expr = if route.body.is_some() { + sanitize_ts_identifier(body_arg) + } else { + "undefined".to_owned() + }; + let auth = !matches!(route.auth, RpcAuth::None); + let path = typescript_path_template(route); + + format!( + " async {method_name}({params}): Promise<{return_type}> {{\n\ + \x20 return this.request<{return_type}>(\"{method}\", {path}, {body_expr}, {auth});\n\ + \x20 }}", + params = params.join(", "), + method = route.method, + ) +} + +fn typescript_path_template(route: &RpcRoute) -> String { + let mut path = route.path.clone(); + for param in &route.path_params { + let ident = sanitize_ts_identifier(¶m.name); + let placeholder = if param.variadic { + format!("<{}..>", param.name) + } else { + format!("<{}>", param.name) + }; + path = path.replace(&placeholder, &format!("${{encodePathValue({ident})}}")); + } + + if !route.query_params.is_empty() { + let query = route + .query_params + .iter() + .map(|param| { + let ident = sanitize_ts_identifier(¶m.name); + format!("{}=${{encodeURIComponent(String({ident}))}}", param.name) + }) + .collect::>() + .join("&"); + path.push('?'); + path.push_str(&query); + } + + format!("`{}`", path.replace('`', "\\`")) +} + +fn typescript_type_declarations(routes: &[&RpcRoute], types: &TsTypes) -> Vec { + let mut names = typescript_type_names(routes); + names.extend(types.models.keys().cloned()); + names.extend(types.enums.keys().cloned()); + names.extend(model_field_type_names(types)); + names + .into_iter() + .map(|name| { + if let Some(model) = types.models.get(&name) { + render_typescript_interface(model) + } else if let Some(ts_enum) = types.enums.get(&name) { + render_typescript_enum(ts_enum) + } else { + format!("export type {name} = unknown;") + } + }) + .collect() +} + +fn render_typescript_enum(ts_enum: &TsEnum) -> String { + let variants = ts_enum + .variants + .iter() + .map(|variant| format!("{variant:?}")) + .collect::>() + .join(" | "); + format!("export type {} = {};", ts_enum.name, variants) +} + +fn render_typescript_interface(model: &TsModel) -> String { + let fields = model + .fields + .iter() + .map(|field| { + let optional = if field.optional { "?" } else { "" }; + format!( + " {}{}: {};", + typescript_property_name(&field.name), + optional, + field.ts_type + ) + }) + .collect::>() + .join("\n"); + + if fields.is_empty() { + format!("export interface {} {{}}", model.name) + } else { + format!("export interface {} {{\n{}\n}}", model.name, fields) + } +} + +fn dart_type_declarations(routes: &[&RpcRoute], types: &TsTypes) -> Vec { + let mut names = typescript_type_names(routes); + names.extend(types.models.keys().cloned()); + names.extend(types.enums.keys().cloned()); + names.extend(model_field_type_names(types)); + names + .into_iter() + .map(|name| { + if let Some(model) = types.models.get(&name) { + render_dart_class(model) + } else if let Some(ts_enum) = types.enums.get(&name) { + render_dart_enum(ts_enum) + } else { + format!("typedef {name} = Object?;") + } + }) + .collect() +} + +fn render_dart_class(model: &TsModel) -> String { + let fields = model + .fields + .iter() + .map(|field| { + format!( + " final {} {};", + dart_type_from_rust(&field.rust_type), + sanitize_dart_identifier(&field.name) + ) + }) + .collect::>() + .join("\n"); + let ctor_params = model + .fields + .iter() + .map(|field| format!("required this.{}", sanitize_dart_identifier(&field.name))) + .collect::>() + .join(", "); + let from_json_fields = model + .fields + .iter() + .map(|field| { + format!( + " {}: {},", + sanitize_dart_identifier(&field.name), + dart_json_decode_expr(&field.rust_type, &format!("json[{:?}]", field.name)) + ) + }) + .collect::>() + .join("\n"); + let to_json_fields = model + .fields + .iter() + .map(|field| { + let ident = sanitize_dart_identifier(&field.name); + format!(" {:?}: _toJson({ident}),", field.name) + }) + .collect::>() + .join("\n"); + + format!( + "class {name} {{\n\ + {fields}\n\n\ + \x20 const {name}({{{ctor_params}}});\n\n\ + \x20 factory {name}.fromJson(Object? value) {{\n\ + \x20 final json = value as Map;\n\ + \x20 return {name}(\n\ + {from_json_fields}\n\ + \x20 );\n\ + \x20 }}\n\n\ + \x20 Map toJson() => {{\n\ + {to_json_fields}\n\ + \x20 }};\n\ + }}", + name = model.name + ) +} + +fn render_dart_enum(ts_enum: &TsEnum) -> String { + let constants = ts_enum + .variants + .iter() + .map(|variant| { + format!( + " {}({:?})", + sanitize_dart_identifier(&to_lower_camel_case(variant)), + variant + ) + }) + .collect::>() + .join(",\n"); + + format!( + "enum {name} {{\n\ + {constants};\n\n\ + \x20 final String value;\n\ + \x20 const {name}(this.value);\n\n\ + \x20 static {name} fromJson(Object? value) {{\n\ + \x20 return {name}.values.firstWhere((item) => item.value == value);\n\ + \x20 }}\n\n\ + \x20 String toJson() => value;\n\ + }}", + name = ts_enum.name + ) +} + +fn render_dart_method(route: &RpcRoute) -> String { + let method_name = to_lower_camel_case(&route.name); + let return_type = route + .response + .as_deref() + .map(dart_type_from_rust) + .unwrap_or_else(|| "Object?".to_owned()); + let mut params = route + .path_params + .iter() + .chain(route.query_params.iter()) + .map(|param| { + let dart_type = if param.variadic { + "List".to_owned() + } else { + dart_type_from_rust(¶m.rust_type) + }; + format!("{} {}", dart_type, sanitize_dart_identifier(¶m.name)) + }) + .collect::>(); + + let body_arg = route.data_param.as_deref().unwrap_or("body"); + if let Some(body) = &route.body { + params.push(format!( + "{} {}", + dart_type_from_rust(body), + sanitize_dart_identifier(body_arg) + )); + } + + let body_expr = if route.body.is_some() { + sanitize_dart_identifier(body_arg) + } else { + "null".to_owned() + }; + let auth = !matches!(route.auth, RpcAuth::None); + let path = dart_path_template(route); + let decode = dart_decode_closure(route.response.as_deref()); + + format!( + " Future<{return_type}> {method_name}({params}) async {{\n\ + \x20 return _request<{return_type}>('{method}', {path}, {body_expr}, {auth}, {decode});\n\ + \x20 }}", + params = params.join(", "), + method = route.method, + ) +} + +fn dart_path_template(route: &RpcRoute) -> String { + let mut path = route.path.clone(); + for param in &route.path_params { + let ident = sanitize_dart_identifier(¶m.name); + let placeholder = if param.variadic { + format!("<{}..>", param.name) + } else { + format!("<{}>", param.name) + }; + path = path.replace(&placeholder, &format!("${{_encodePathValue({ident})}}")); + } + if !route.query_params.is_empty() { + let query = route + .query_params + .iter() + .map(|param| { + let ident = sanitize_dart_identifier(¶m.name); + format!("{}=${{Uri.encodeQueryComponent('${ident}')}}", param.name) + }) + .collect::>() + .join("&"); + path.push('?'); + path.push_str(&query); + } + format!("'{path}'") +} + +fn dart_decode_closure(rust_type: Option<&str>) -> String { + match rust_type { + Some(rust_type) => format!("(value) => {}", dart_json_decode_expr(rust_type, "value")), + None => "(_) => null".to_owned(), + } +} + +fn dart_json_decode_expr(rust_type: &str, value: &str) -> String { + let rust_type = rust_type.trim(); + if let Some(inner) = generic_inner_for_last_segment(rust_type, "Option") { + return format!( + "{value} == null ? null : {}", + dart_json_decode_expr(&inner, value) + ); + } + if let Some(inner) = generic_inner_for_last_segment(rust_type, "Vec") { + return format!( + "({value} as List).map((item) => {}).toList()", + dart_json_decode_expr(&inner, "item") + ); + } + + match rust_type { + "String" | "& str" | "&str" | "& 'static str" => format!("{value} as String"), + "bool" => format!("{value} as bool"), + "i8" | "i16" | "i32" | "i64" | "isize" | "u8" | "u16" | "u32" | "u64" | "usize" => { + format!("{value} as int") + } + "f32" | "f64" => format!("({value} as num).toDouble()"), + other => { + let name = type_last_segment(other).unwrap_or(other).replace(' ', ""); + if is_valid_ts_type_name(&name) { + format!("{name}.fromJson({value})") + } else { + value.to_owned() + } + } + } +} + +fn dart_type_from_rust(rust_type: &str) -> String { + let rust_type = rust_type.trim(); + if let Some(inner) = generic_inner_for_last_segment(rust_type, "Option") { + return format!("{}?", dart_type_from_rust(&inner).trim_end_matches('?')); + } + if let Some(inner) = generic_inner_for_last_segment(rust_type, "Vec") { + return format!("List<{}>", dart_type_from_rust(&inner)); + } + + match rust_type { + "String" | "& str" | "&str" | "& 'static str" => "String".to_owned(), + "bool" => "bool".to_owned(), + "i8" | "i16" | "i32" | "i64" | "isize" | "u8" | "u16" | "u32" | "u64" | "usize" => { + "int".to_owned() + } + "f32" | "f64" => "double".to_owned(), + "()" => "void".to_owned(), + other => type_last_segment(other).unwrap_or(other).replace(' ', ""), + } +} + +fn rust_type_declarations(routes: &[&RpcRoute], types: &TsTypes) -> Vec { + let mut names = typescript_type_names(routes); + names.extend(types.models.keys().cloned()); + names.extend(types.enums.keys().cloned()); + names.extend(model_field_type_names(types)); + names + .into_iter() + .map(|name| { + if let Some(model) = types.models.get(&name) { + render_rust_struct(model) + } else if let Some(ts_enum) = types.enums.get(&name) { + render_rust_enum(ts_enum) + } else { + format!("pub type {name} = serde_json::Value;") + } + }) + .collect() +} + +fn render_rust_struct(model: &TsModel) -> String { + let fields = model + .fields + .iter() + .map(|field| { + let ident = sanitize_rust_identifier(&field.name); + let rename = if ident == field.name { + String::new() + } else { + format!(" #[serde(rename = {:?})]\n", field.name) + }; + format!( + "{rename} pub {ident}: {},", + client_rust_type(&field.rust_type) + ) + }) + .collect::>() + .join("\n"); + + if fields.is_empty() { + format!( + "#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct {} {{}}", + model.name + ) + } else { + format!( + "#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct {} {{\n{}\n}}", + model.name, fields + ) + } +} + +fn render_rust_enum(ts_enum: &TsEnum) -> String { + let variants = ts_enum + .variants + .iter() + .map(|variant| { + let rust_name = to_pascal_case(variant); + format!(" #[serde(rename = {:?})]\n {rust_name},", variant) + }) + .collect::>() + .join("\n"); + + format!( + "#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]\npub enum {} {{\n{}\n}}", + ts_enum.name, variants + ) +} + +fn render_rust_method(route: &RpcRoute) -> String { + let method_name = route.name.clone(); + let return_type = route + .response + .as_deref() + .map(client_rust_type) + .unwrap_or_else(|| "serde_json::Value".to_owned()); + let mut params = route + .path_params + .iter() + .chain(route.query_params.iter()) + .map(|param| { + let rust_type = if param.variadic { + "impl IntoIterator".to_owned() + } else { + client_rust_type(¶m.rust_type) + }; + format!("{}: {}", sanitize_rust_identifier(¶m.name), rust_type) + }) + .collect::>(); + + let body_arg = route.data_param.as_deref().unwrap_or("body"); + if let Some(body) = &route.body { + params.push(format!( + "{}: &{}", + sanitize_rust_identifier(body_arg), + client_rust_type(body) + )); + } + + let path_expr = rust_path_expr(route); + let body_expr = if route.body.is_some() { + format!("Some({})", sanitize_rust_identifier(body_arg)) + } else { + "Option::<&()>::None".to_owned() + }; + let auth = !matches!(route.auth, RpcAuth::None); + + let signature_params = if params.is_empty() { + "&self".to_owned() + } else { + format!("&self, {}", params.join(", ")) + }; + + format!( + " pub async fn {method_name}({signature_params}) -> Result<{return_type}, CometRpcError> {{\n\ + \x20 self.request(reqwest::Method::{method}, {path_expr}, {body_expr}, {auth}).await\n\ + \x20 }}", + method = route.method, + ) +} + +fn rust_path_expr(route: &RpcRoute) -> String { + if route.path_params.is_empty() && route.query_params.is_empty() { + return format!("{:?}.to_owned()", route.path); + } + + let mut format_string = route.path.clone(); + let mut args = Vec::new(); + for param in &route.path_params { + let placeholder = if param.variadic { + format!("<{}..>", param.name) + } else { + format!("<{}>", param.name) + }; + format_string = format_string.replace(&placeholder, "{}"); + let ident = sanitize_rust_identifier(¶m.name); + if param.variadic { + args.push(format!( + "{ident}.into_iter().map(encode_path_value).collect::>().join(\"/\")" + )); + } else { + args.push(format!("encode_path_value({ident})")); + } + } + + if !route.query_params.is_empty() { + let query = route + .query_params + .iter() + .map(|param| { + let ident = sanitize_rust_identifier(¶m.name); + args.push(format!("encode_path_value({ident})")); + format!("{}={{}}", param.name) + }) + .collect::>() + .join("&"); + format_string.push('?'); + format_string.push_str(&query); + } + + format!("format!({:?}, {})", format_string, args.join(", ")) +} + +fn client_rust_type(rust_type: &str) -> String { + let rust_type = rust_type.trim(); + if let Some(inner) = generic_inner_for_last_segment(rust_type, "Option") { + return format!("Option<{}>", client_rust_type(&inner)); + } + if let Some(inner) = generic_inner_for_last_segment(rust_type, "Vec") { + return format!("Vec<{}>", client_rust_type(&inner)); + } + + match rust_type { + "String" | "& str" | "&str" | "& 'static str" => "String".to_owned(), + "bool" => "bool".to_owned(), + "i8" | "i16" | "i32" | "i64" | "isize" | "u8" | "u16" | "u32" | "u64" | "usize" | "f32" + | "f64" => rust_type.to_owned(), + "()" => "()".to_owned(), + other => type_last_segment(other).unwrap_or(other).replace(' ', ""), + } +} + +fn sanitize_rust_identifier(value: &str) -> String { + let mut ident = String::new(); + for (index, ch) in value.chars().enumerate() { + if ch == '_' || ch.is_ascii_alphanumeric() { + if index == 0 && ch.is_ascii_digit() { + ident.push('_'); + } + ident.push(ch); + } else { + ident.push('_'); + } + } + + match ident.as_str() { + "" => "value".to_owned(), + "type" | "match" | "ref" | "self" | "Self" | "crate" | "super" | "enum" | "struct" + | "fn" | "async" | "await" | "impl" | "trait" | "where" | "move" | "loop" | "for" + | "while" | "use" | "mod" | "pub" | "const" | "static" | "let" => format!("r#{ident}"), + _ => ident, + } +} + +fn to_pascal_case(value: &str) -> String { + let mut output = String::new(); + for part in value + .split(|ch: char| ch == '_' || ch == '-' || ch.is_whitespace()) + .filter(|part| !part.is_empty()) + { + let mut chars = part.chars(); + if let Some(first) = chars.next() { + output.push(first.to_ascii_uppercase()); + output.push_str(&chars.as_str().to_ascii_lowercase()); + } + } + if output.is_empty() { + "Value".to_owned() + } else if output.chars().next().is_some_and(|ch| ch.is_ascii_digit()) { + format!("Value{output}") + } else { + output + } +} + +fn typescript_type_names_for_manifest( + manifest: &RpcManifest, +) -> std::collections::BTreeSet { + let routes = manifest + .routes + .iter() + .filter(|route| route.support == RpcRouteSupport::Json) + .collect::>(); + typescript_type_names(&routes) +} + +fn typescript_type_names(routes: &[&RpcRoute]) -> std::collections::BTreeSet { + let mut names = std::collections::BTreeSet::new(); + for route in routes { + if let Some(body) = &route.body { + collect_custom_type_names(body, &mut names); + } + if let Some(response) = &route.response { + collect_custom_type_names(response, &mut names); + } + } + names +} + +fn model_field_type_names(types: &TsTypes) -> std::collections::BTreeSet { + let mut names = std::collections::BTreeSet::new(); + for model in types.models.values() { + for field in &model.fields { + collect_custom_type_names(&field.rust_type, &mut names); + } + } + names +} + +fn collect_custom_type_names(rust_type: &str, names: &mut std::collections::BTreeSet) { + let rust_type = rust_type.trim(); + for wrapper in ["Json", "Vec", "Option"] { + if let Some(inner) = generic_inner_for_last_segment(rust_type, wrapper) { + for part in split_top_level_commas(&inner) { + collect_custom_type_names(&part, names); + } + return; + } + } + + if is_primitive_rust_type(rust_type) { + return; + } + + let name = type_last_segment(rust_type).unwrap_or(rust_type).trim(); + if is_valid_ts_type_name(name) { + names.insert(name.to_owned()); + } +} + +fn typescript_property_name(name: &str) -> String { + if is_valid_ts_identifier(name) { + name.to_owned() + } else { + format!("{name:?}") + } +} + +fn rust_type_to_typescript(rust_type: &str) -> String { + let rust_type = rust_type.trim(); + if let Some(inner) = generic_inner_for_last_segment(rust_type, "Vec") { + return format!("Array<{}>", rust_type_to_typescript(&inner)); + } + if let Some(inner) = generic_inner_for_last_segment(rust_type, "Option") { + return format!("{} | null", rust_type_to_typescript(&inner)); + } + + match rust_type { + "String" | "& str" | "&str" | "& 'static str" => "string".to_owned(), + "bool" => "boolean".to_owned(), + "i8" | "i16" | "i32" | "i64" | "isize" | "u8" | "u16" | "u32" | "u64" | "usize" | "f32" + | "f64" => "number".to_owned(), + "()" => "void".to_owned(), + other => type_last_segment(other).unwrap_or(other).replace(' ', ""), + } +} + +fn is_primitive_rust_type(rust_type: &str) -> bool { + matches!( + rust_type, + "String" + | "& str" + | "&str" + | "& 'static str" + | "bool" + | "i8" + | "i16" + | "i32" + | "i64" + | "isize" + | "u8" + | "u16" + | "u32" + | "u64" + | "usize" + | "f32" + | "f64" + | "()" + ) +} + +fn to_lower_camel_case(value: &str) -> String { + let mut output = String::new(); + for (index, part) in value + .split(|ch: char| ch == '_' || ch == '-' || ch.is_whitespace()) + .filter(|part| !part.is_empty()) + .enumerate() + { + if index == 0 { + output.push_str(&part.to_ascii_lowercase()); + } else { + let mut chars = part.chars(); + if let Some(first) = chars.next() { + output.push(first.to_ascii_uppercase()); + output.push_str(&chars.as_str().to_ascii_lowercase()); + } + } + } + output +} + +fn sanitize_ts_identifier(value: &str) -> String { + let mut ident = String::new(); + for (index, ch) in value.chars().enumerate() { + if ch == '_' || ch.is_ascii_alphanumeric() { + if index == 0 && ch.is_ascii_digit() { + ident.push('_'); + } + ident.push(ch); + } else { + ident.push('_'); + } + } + + if ident.is_empty() { + "value".to_owned() + } else { + ident + } +} + +fn sanitize_dart_identifier(value: &str) -> String { + let mut ident = String::new(); + for (index, ch) in value.chars().enumerate() { + if ch == '_' || ch.is_ascii_alphanumeric() { + if index == 0 && ch.is_ascii_digit() { + ident.push('_'); + } + ident.push(ch); + } else { + ident.push('_'); + } + } + + match ident.as_str() { + "" => "value".to_owned(), + "class" | "enum" | "final" | "import" | "return" | "void" | "true" | "false" | "null" => { + format!("{ident}_") + } + _ => ident, + } +} + +fn is_valid_ts_type_name(value: &str) -> bool { + let mut chars = value.chars(); + let Some(first) = chars.next() else { + return false; + }; + (first == '_' || first.is_ascii_alphabetic()) + && chars.all(|ch| ch == '_' || ch.is_ascii_alphanumeric()) +} + +fn is_valid_ts_identifier(value: &str) -> bool { + let mut chars = value.chars(); + let Some(first) = chars.next() else { + return false; + }; + (first == '_' || first == '$' || first.is_ascii_alphabetic()) + && chars.all(|ch| ch == '_' || ch == '$' || ch.is_ascii_alphanumeric()) +} + +fn resolve_authorization_policies(project_dir: &Path, routes: &mut [RpcRoute]) -> Result<()> { + let pending = routes + .iter() + .filter_map(|route| { + let RpcAuth::Authorized { + policy: Some(policy), + roles, + permissions, + scopes, + .. + } = &route.auth + else { + return None; + }; + + if !roles.is_empty() || !permissions.is_empty() || !scopes.is_empty() { + return None; + } + + Some(PolicyRef { + display: policy.clone(), + path: String::new(), + module_path: route.module_path.clone(), + }) + }) + .collect::>(); + + if pending.is_empty() { + return Ok(()); + } + + if !project_dir.join("Cargo.toml").exists() { + for route in routes { + if let RpcAuth::Authorized { + policy: Some(policy), + roles, + permissions, + scopes, + .. + } = &route.auth + && roles.is_empty() + && permissions.is_empty() + && scopes.is_empty() + { + route.warnings.push(format!( + "authorization policy `{policy}` was not resolved because the project has no Cargo.toml" + )); + } + } + return Ok(()); + } + + let project = RustProject::read(project_dir)?; + let policy_refs = pending + .into_iter() + .map(|policy_ref| PolicyRef { + path: policy_path( + &policy_ref.display, + &policy_ref.module_path, + &project.crate_name, + ), + ..policy_ref + }) + .collect::>(); + let requirements = dump_authorization_requirements(&project, &policy_refs)?; + + for route in routes { + let RpcAuth::Authorized { + policy: Some(policy), + roles, + permissions, + scopes, + resource, + mode, + } = &mut route.auth + else { + continue; + }; + + if !roles.is_empty() || !permissions.is_empty() || !scopes.is_empty() { + continue; + } + + let path = policy_path(policy, &route.module_path, &project.crate_name); + if let Some(requirement) = requirements.iter().find(|entry| entry.path == path) { + *roles = requirement.roles.clone(); + *permissions = requirement.permissions.clone(); + *scopes = requirement.scopes.clone(); + *resource = requirement.resource.clone(); + *mode = requirement.mode.clone(); + } else { + route.warnings.push(format!( + "authorization policy `{policy}` could not be resolved" + )); + } + } + + Ok(()) +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +struct PolicyRef { + display: String, + path: String, + module_path: Vec, +} + +#[derive(Debug)] +struct RustProject { + dir: PathBuf, + package_name: String, + crate_name: String, + comet_auth_dependency: Value, +} + +#[derive(Debug, Deserialize)] +struct AuthorizationRequirementDump { + path: String, + mode: AuthMode, + roles: Vec, + permissions: Vec, + scopes: Vec, + resource: Option, +} + +impl<'de> Deserialize<'de> for AuthMode { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + match value.as_str() { + "all" => Ok(Self::All), + "any" => Ok(Self::Any), + other => Err(serde::de::Error::custom(format!( + "unsupported authorization mode `{other}`" + ))), + } + } +} + +impl RustProject { + fn read(project_dir: &Path) -> Result { + let dir = project_dir + .canonicalize() + .with_context(|| format!("resolving {}", project_dir.display()))?; + let cargo_toml_path = dir.join("Cargo.toml"); + let cargo_toml_text = fs::read_to_string(&cargo_toml_path) + .with_context(|| format!("reading {}", cargo_toml_path.display()))?; + let root: Value = toml::from_str(&cargo_toml_text) + .with_context(|| format!("parsing {}", cargo_toml_path.display()))?; + + let package_name = root + .get("package") + .and_then(|package| package.get("name")) + .and_then(Value::as_str) + .with_context(|| format!("{} has no [package].name", cargo_toml_path.display()))? + .to_owned(); + let crate_name = package_name.replace('-', "_"); + + let comet_auth_dependency = root + .get("dependencies") + .and_then(|deps| deps.get("comet-auth")) + .with_context(|| { + format!( + "{} has no [dependencies].comet-auth entry", + cargo_toml_path.display() + ) + })? + .clone(); + let mut comet_auth_dependency = dependency_as_table(&comet_auth_dependency)?; + resolve_relative_path(&mut comet_auth_dependency, &dir)?; + + Ok(Self { + dir, + package_name, + crate_name, + comet_auth_dependency, + }) + } +} + +fn dump_authorization_requirements( + project: &RustProject, + policy_refs: &[PolicyRef], +) -> Result> { + if policy_refs.is_empty() { + return Ok(Vec::new()); + } + + let mut policy_refs = policy_refs.to_vec(); + policy_refs.sort(); + policy_refs.dedup_by(|a, b| a.path == b.path); + + let temp_dir = tempfile::tempdir().context("creating rpc-dump temp directory")?; + fs::write( + temp_dir.path().join("Cargo.toml"), + build_rpc_dump_manifest(project)?, + ) + .context("writing rpc-dump Cargo.toml")?; + fs::create_dir_all(temp_dir.path().join("src")).context("creating rpc-dump src/")?; + fs::write( + temp_dir.path().join("src/main.rs"), + render_auth_dump_main_rs(&policy_refs), + ) + .context("writing rpc-dump main.rs")?; + + let output = Command::new("cargo") + .arg("run") + .arg("--quiet") + .current_dir(temp_dir.path()) + .output() + .context("running cargo for the rpc-dump crate")?; + + if !output.status.success() { + bail!( + "RPC authorization policy dump failed:\n{}", + String::from_utf8_lossy(&output.stderr) + ); + } + + let json = + String::from_utf8(output.stdout).context("RPC authorization dump produced non-UTF8")?; + serde_json::from_str(&json).context("parsing RPC authorization dump JSON") +} + +fn build_rpc_dump_manifest(project: &RustProject) -> Result { + let mut target_dependency = toml::Table::new(); + target_dependency.insert( + "path".to_owned(), + Value::String(project.dir.display().to_string()), + ); + + let mut dependencies = toml::Table::new(); + dependencies.insert( + project.package_name.clone(), + Value::Table(target_dependency), + ); + dependencies.insert( + "comet-auth".to_owned(), + project.comet_auth_dependency.clone(), + ); + dependencies.insert("serde_json".to_owned(), Value::String("1".to_owned())); + + let mut package = toml::Table::new(); + package.insert( + "name".to_owned(), + Value::String("comet-rpc-dump".to_owned()), + ); + package.insert("version".to_owned(), Value::String("0.0.0".to_owned())); + package.insert("edition".to_owned(), Value::String("2021".to_owned())); + package.insert("publish".to_owned(), Value::Boolean(false)); + + let mut root = toml::Table::new(); + root.insert("package".to_owned(), Value::Table(package)); + root.insert("dependencies".to_owned(), Value::Table(dependencies)); + + toml::to_string_pretty(&Value::Table(root)).context("rendering rpc-dump Cargo.toml") +} + +fn render_auth_dump_main_rs(policy_refs: &[PolicyRef]) -> String { + let entries = policy_refs + .iter() + .map(|policy_ref| { + let path = &policy_ref.path; + format!( + " {{\n\ + \x20 let requirement = <{path} as ::comet_auth::RequiredAuthorization>::REQUIREMENT;\n\ + \x20 values.push(::serde_json::json!({{\n\ + \x20 \"path\": \"{path}\",\n\ + \x20 \"mode\": match requirement.mode {{\n\ + \x20 ::comet_auth::AuthorizationMode::All => \"all\",\n\ + \x20 ::comet_auth::AuthorizationMode::Any => \"any\",\n\ + \x20 }},\n\ + \x20 \"roles\": requirement.roles,\n\ + \x20 \"permissions\": requirement.permissions,\n\ + \x20 \"scopes\": requirement.scopes,\n\ + \x20 \"resource\": requirement.resource,\n\ + \x20 }}));\n\ + \x20 }}" + ) + }) + .collect::>() + .join("\n"); + + format!( + "fn main() {{\n\ + \x20 let mut values = Vec::new();\n\ + {entries}\n\ + \x20 print!(\"{{}}\", ::serde_json::to_string(&values).unwrap());\n\ + }}\n" + ) +} + +fn dependency_as_table(dependency: &Value) -> Result { + match dependency { + Value::String(version) => { + let mut table = toml::Table::new(); + table.insert("version".to_owned(), Value::String(version.clone())); + Ok(Value::Table(table)) + } + Value::Table(table) => Ok(Value::Table(table.clone())), + other => bail!("unsupported dependency format in Cargo.toml: {other:?}"), + } +} + +fn resolve_relative_path(dependency: &mut Value, base_dir: &Path) -> Result<()> { + let Value::Table(table) = dependency else { + return Ok(()); + }; + let Some(Value::String(path_str)) = table.get("path").cloned() else { + return Ok(()); + }; + + let path = Path::new(&path_str); + let absolute = if path.is_absolute() { + path.to_path_buf() + } else { + base_dir.join(path) + }; + let absolute = absolute + .canonicalize() + .with_context(|| format!("resolving path dependency {}", absolute.display()))?; + + table.insert( + "path".to_owned(), + Value::String(absolute.display().to_string()), + ); + Ok(()) +} + +fn policy_path(policy: &str, module_path: &[String], crate_name: &str) -> String { + if let Some(rest) = policy.strip_prefix("crate ::") { + return format!("{crate_name}::{rest}").replace(" :: ", "::"); + } + if let Some(rest) = policy.strip_prefix("crate::") { + return format!("{crate_name}::{rest}"); + } + if policy.starts_with("::") { + return policy.trim_start_matches("::").replace(" :: ", "::"); + } + if policy.contains("::") || policy.contains(" :: ") { + return policy.replace(" :: ", "::"); + } + + let mut segments = vec![crate_name.to_owned()]; + segments.extend(module_path.iter().cloned()); + segments.push(policy.to_owned()); + segments.join("::") +} + +fn visit_dir( + dir: &Path, + module_path: &[String], + result_aliases: &[ResultAlias], + routes: &mut Vec, +) -> Result<()> { + let mut dir_entries = fs::read_dir(dir) + .with_context(|| format!("reading directory {}", dir.display()))? + .collect::>>() + .with_context(|| format!("reading directory {}", dir.display()))?; + dir_entries.sort_by_key(|entry| entry.file_name()); + + for entry in dir_entries { + let path = entry.path(); + let file_type = entry + .file_type() + .with_context(|| format!("reading file type of {}", path.display()))?; + + if file_type.is_dir() { + let mut nested_path = module_path.to_vec(); + nested_path.push(entry.file_name().to_string_lossy().into_owned()); + visit_dir(&path, &nested_path, result_aliases, routes)?; + continue; + } + + if path.extension().and_then(|ext| ext.to_str()) == Some("rs") { + visit_file(&path, module_path, result_aliases, routes)?; + } + } + + Ok(()) +} + +fn visit_file( + path: &Path, + module_path: &[String], + result_aliases: &[ResultAlias], + routes: &mut Vec, +) -> Result<()> { + let file_module_path = file_module_path(path, module_path); + let source = fs::read_to_string(path).with_context(|| format!("reading {}", path.display()))?; + let file = syn::parse_file(&source).with_context(|| format!("parsing {}", path.display()))?; + + for item in &file.items { + if let Item::Fn(item_fn) = item { + for route_attr in rocket_route_attrs(item_fn) { + routes.push(route_from_fn( + path, + &file_module_path, + item_fn, + route_attr, + &result_aliases, + )); + } + } + } + + Ok(()) +} + +fn file_module_path(path: &Path, module_path: &[String]) -> Vec { + let stem = path + .file_stem() + .and_then(|stem| stem.to_str()) + .unwrap_or_default(); + + match stem { + "mod" | "lib" | "main" => module_path.to_vec(), + _ => { + let mut nested = module_path.to_vec(); + nested.push(stem.to_owned()); + nested + } + } +} + +fn rocket_route_attrs(item_fn: &ItemFn) -> Vec { + item_fn + .attrs + .iter() + .filter_map(|attr| { + let method = route_method(attr.path().segments.last()?.ident.to_string().as_str())?; + let args = attr.parse_args::().ok()?; + Some(RocketRouteAttr { + method: method.to_owned(), + path: args.path.value(), + data_param: args.data_param, + }) + }) + .collect() +} + +fn discover_project_result_aliases(src_dir: &Path) -> Result> { + let mut aliases = Vec::new(); + visit_result_alias_dir(src_dir, &mut aliases)?; + aliases.sort_by(|a, b| { + a.name + .cmp(&b.name) + .then_with(|| a.error_type.cmp(&b.error_type)) + }); + aliases.dedup(); + Ok(aliases) +} + +fn visit_result_alias_dir(dir: &Path, aliases: &mut Vec) -> Result<()> { + let mut dir_entries = fs::read_dir(dir) + .with_context(|| format!("reading directory {}", dir.display()))? + .collect::>>() + .with_context(|| format!("reading directory {}", dir.display()))?; + dir_entries.sort_by_key(|entry| entry.file_name()); + + for entry in dir_entries { + let path = entry.path(); + let file_type = entry + .file_type() + .with_context(|| format!("reading file type of {}", path.display()))?; + + if file_type.is_dir() { + visit_result_alias_dir(&path, aliases)?; + } else if path.extension().and_then(|ext| ext.to_str()) == Some("rs") { + let source = + fs::read_to_string(&path).with_context(|| format!("reading {}", path.display()))?; + let file = + syn::parse_file(&source).with_context(|| format!("parsing {}", path.display()))?; + aliases.extend(discover_result_aliases(&file)); + } + } + + Ok(()) +} + +fn discover_result_aliases(file: &syn::File) -> Vec { + file.items + .iter() + .filter_map(|item| { + let Item::Type(item_type) = item else { + return None; + }; + let alias_name = item_type.ident.to_string(); + let Type::Path(type_path) = item_type.ty.as_ref() else { + return None; + }; + let segment = type_path.path.segments.last()?; + if segment.ident != "Result" { + return None; + } + let PathArguments::AngleBracketed(args) = &segment.arguments else { + return None; + }; + let mut type_args = args.args.iter().filter_map(|arg| match arg { + GenericArgument::Type(ty) => Some(type_to_string(ty)), + _ => None, + }); + let _ok = type_args.next()?; + let error_type = type_args.next()?; + + Some(ResultAlias { + name: alias_name, + error_type, + }) + }) + .collect() +} + +fn route_method(ident: &str) -> Option<&'static str> { + match ident { + "get" => Some("GET"), + "post" => Some("POST"), + "put" => Some("PUT"), + "delete" => Some("DELETE"), + "patch" => Some("PATCH"), + _ => None, + } +} + +fn route_from_fn( + path: &Path, + module_path: &[String], + item_fn: &ItemFn, + route_attr: RocketRouteAttr, + result_aliases: &[ResultAlias], +) -> RpcRoute { + let mut warnings = Vec::new(); + let inputs = route_inputs(item_fn); + let (http_path, query_placeholders) = split_route_path_and_query(&route_attr.path); + let path_params = discover_path_params(&http_path, &inputs, &mut warnings); + let query_params = discover_query_params(&query_placeholders, &inputs, &mut warnings); + let body = route_attr + .data_param + .as_deref() + .and_then(|name| inputs.iter().find(|input| input.name == name)) + .and_then(|input| json_inner_type(&input.rust_type)); + let (response, error) = response_and_error(item_fn, result_aliases); + let auth = auth_for_route(item_fn, &inputs); + let support = classify_support( + &inputs, + response.as_deref(), + error.as_deref(), + body.as_deref(), + ); + + if route_attr.data_param.is_some() && body.is_none() { + warnings.push("data parameter is not a supported Json body".to_owned()); + } + + if response.is_none() && support == RpcRouteSupport::Json { + warnings.push("response type could not be inferred".to_owned()); + } + + RpcRoute { + name: item_fn.sig.ident.to_string(), + module_path: module_path.to_vec(), + source: path.display().to_string(), + method: route_attr.method, + path: http_path, + data_param: route_attr.data_param, + path_params, + query_params, + body, + response, + error, + auth, + support, + warnings, + } +} + +#[derive(Debug)] +struct RouteInput { + name: String, + rust_type: String, +} + +fn route_inputs(item_fn: &ItemFn) -> Vec { + item_fn + .sig + .inputs + .iter() + .filter_map(|input| { + let FnArg::Typed(pat_type) = input else { + return None; + }; + let Pat::Ident(pat_ident) = pat_type.pat.as_ref() else { + return None; + }; + + Some(RouteInput { + name: pat_ident.ident.to_string(), + rust_type: type_to_string(&pat_type.ty), + }) + }) + .collect() +} + +fn discover_path_params( + path: &str, + inputs: &[RouteInput], + warnings: &mut Vec, +) -> Vec { + let mut params = Vec::new(); + for segment in path.split('/') { + let Some(raw) = segment.strip_prefix('<').and_then(|s| s.strip_suffix('>')) else { + continue; + }; + + let (name, variadic) = match raw.strip_suffix("..") { + Some(name) => (name, true), + None => (raw, false), + }; + + match inputs.iter().find(|input| input.name == name) { + Some(input) => params.push(RpcParam { + name: name.to_owned(), + rust_type: input.rust_type.clone(), + variadic, + }), + None => warnings.push(format!("path parameter `{name}` has no matching argument")), + } + } + params +} + +fn split_route_path_and_query(path: &str) -> (String, Vec) { + let Some((path, query)) = path.split_once('?') else { + return (path.to_owned(), Vec::new()); + }; + + let placeholders = query + .split('&') + .filter_map(|segment| { + let raw = segment.trim(); + raw.strip_prefix('<') + .and_then(|value| value.strip_suffix('>')) + .map(str::to_owned) + }) + .collect(); + + (path.to_owned(), placeholders) +} + +fn discover_query_params( + placeholders: &[String], + inputs: &[RouteInput], + warnings: &mut Vec, +) -> Vec { + placeholders + .iter() + .filter_map( + |name| match inputs.iter().find(|input| input.name == *name) { + Some(input) => Some(RpcParam { + name: name.clone(), + rust_type: input.rust_type.clone(), + variadic: false, + }), + None => { + warnings.push(format!("query parameter `{name}` has no matching argument")); + None + } + }, + ) + .collect() +} + +fn response_and_error( + item_fn: &ItemFn, + result_aliases: &[ResultAlias], +) -> (Option, Option) { + let ReturnType::Type(_, ty) = &item_fn.sig.output else { + return (None, None); + }; + + let output = type_to_string(ty); + if let Some((ok, err)) = split_result_like(&output, "Result") + .or_else(|| split_result_alias(&output, result_aliases)) + .or_else(|| split_result_like(&output, "ApiResult")) + { + return (json_inner_type(&ok).or(Some(ok)), err); + } + + if let Some(inner) = json_inner_type(&output) { + return (Some(inner), None); + } + + (None, Some(output)) +} + +fn split_result_like(output: &str, wrapper: &str) -> Option<(String, Option)> { + let generic = generic_inner_for_last_segment(output, wrapper)?; + let parts = split_top_level_commas(&generic); + match parts.as_slice() { + [ok] if wrapper == "ApiResult" => Some((ok.to_string(), Some(wrapper.to_owned()))), + [ok, err] => Some((ok.to_string(), Some(err.to_string()))), + _ => None, + } +} + +fn split_result_alias(output: &str, aliases: &[ResultAlias]) -> Option<(String, Option)> { + aliases.iter().find_map(|alias| { + let ok = generic_inner_for_last_segment(output, &alias.name)?; + Some((ok, Some(alias.error_type.clone()))) + }) +} + +fn auth_for_route(item_fn: &ItemFn, inputs: &[RouteInput]) -> RpcAuth { + if let Some(auth) = auth_from_requires_auth_attr(item_fn) { + return auth; + } + + for input in inputs { + if type_last_segment(&input.rust_type) == Some("OptionalAuthSession") { + return RpcAuth::Optional; + } + } + + for input in inputs { + match type_last_segment(&input.rust_type) { + Some("AuthSession") => return RpcAuth::Required, + Some("AuthorizedSession") => { + return RpcAuth::Authorized { + policy: generic_inner_for_last_segment(&input.rust_type, "AuthorizedSession"), + roles: Vec::new(), + permissions: Vec::new(), + scopes: Vec::new(), + resource: None, + mode: AuthMode::All, + }; + } + _ => {} + } + } + + RpcAuth::None +} + +fn auth_from_requires_auth_attr(item_fn: &ItemFn) -> Option { + let attr = item_fn.attrs.iter().find(|attr| { + attr.path() + .segments + .last() + .is_some_and(|segment| segment.ident == "requires_auth") + })?; + + let args = attr + .parse_args_with(Punctuated::::parse_terminated) + .ok()?; + let mut auth = ParsedRequiresAuth::default(); + for arg in args { + match arg { + RequiresAuthArg::Optional => auth.optional = true, + RequiresAuthArg::Resource(resource) => auth.resource = Some(resource), + RequiresAuthArg::Claim(claim) => auth.push(claim), + RequiresAuthArg::Group { mode, claims } => { + auth.mode = mode; + for claim in claims { + auth.push(claim); + } + } + } + } + + if auth.optional { + Some(RpcAuth::Optional) + } else if auth.has_policy() { + Some(RpcAuth::Authorized { + policy: None, + roles: auth.roles, + permissions: auth.permissions, + scopes: auth.scopes, + resource: auth.resource, + mode: auth.mode, + }) + } else { + Some(RpcAuth::Required) + } +} + +#[derive(Default)] +struct ParsedRequiresAuth { + optional: bool, + roles: Vec, + permissions: Vec, + scopes: Vec, + resource: Option, + mode: AuthMode, +} + +impl ParsedRequiresAuth { + fn push(&mut self, claim: RequiresAuthClaim) { + match claim { + RequiresAuthClaim::Role(value) => self.roles.push(value), + RequiresAuthClaim::Permission(value) => self.permissions.push(value), + RequiresAuthClaim::Scope(value) => self.scopes.push(value), + } + } + + fn has_policy(&self) -> bool { + !self.roles.is_empty() || !self.permissions.is_empty() || !self.scopes.is_empty() + } +} + +impl Default for AuthMode { + fn default() -> Self { + Self::All + } +} + +enum RequiresAuthArg { + Optional, + Resource(String), + Claim(RequiresAuthClaim), + Group { + mode: AuthMode, + claims: Vec, + }, +} + +enum RequiresAuthClaim { + Role(String), + Permission(String), + Scope(String), +} + +impl Parse for RequiresAuthArg { + fn parse(input: ParseStream<'_>) -> syn::Result { + let name = input.parse::()?; + match name.to_string().as_str() { + "optional" => Ok(Self::Optional), + "resource" => { + input.parse::()?; + Ok(Self::Resource(input.parse::()?.value())) + } + "role" | "permission" | "scope" => { + input.parse::()?; + claim_from_name(&name.to_string(), input.parse::()?.value()) + .map(Self::Claim) + } + "any" | "all" => { + let mode = if name == "any" { + AuthMode::Any + } else { + AuthMode::All + }; + let content; + syn::parenthesized!(content in input); + let parsed = + Punctuated::::parse_terminated(&content)?; + Ok(Self::Group { + mode, + claims: parsed.into_iter().collect(), + }) + } + _ => Err(syn::Error::new_spanned( + name, + "unsupported requires_auth argument", + )), + } + } +} + +impl Parse for RequiresAuthClaim { + fn parse(input: ParseStream<'_>) -> syn::Result { + let name = input.parse::()?; + input.parse::()?; + claim_from_name(&name.to_string(), input.parse::()?.value()) + } +} + +fn claim_from_name(name: &str, value: String) -> syn::Result { + match name { + "role" => Ok(RequiresAuthClaim::Role(value)), + "permission" => Ok(RequiresAuthClaim::Permission(value)), + "scope" => Ok(RequiresAuthClaim::Scope(value)), + _ => Err(syn::Error::new( + proc_macro2::Span::call_site(), + "unsupported requires_auth claim", + )), + } +} + +fn classify_support( + inputs: &[RouteInput], + response: Option<&str>, + error_or_responder: Option<&str>, + body: Option<&str>, +) -> RpcRouteSupport { + if inputs + .iter() + .any(|input| is_raw_or_stream_type(&input.rust_type)) + || response.is_some_and(is_raw_or_stream_type) + || error_or_responder.is_some_and(is_raw_or_stream_type) + { + return RpcRouteSupport::Raw; + } + + if body.is_some() || response.is_some() { + return RpcRouteSupport::Json; + } + + RpcRouteSupport::Unsupported +} + +fn is_raw_or_stream_type(rust_type: &str) -> bool { + [ + "ByteStream", + "WebSocketResponse", + "WebSocketUpgrade", + "R2Object", + "Capped", + "Status", + "String", + "Vec < u8 >", + "Vec", + ] + .iter() + .any(|needle| rust_type.contains(needle)) +} + +fn json_inner_type(rust_type: &str) -> Option { + generic_inner_for_last_segment(rust_type, "Json") +} + +fn generic_inner_for_last_segment(rust_type: &str, segment: &str) -> Option { + let patterns = [ + format!("{segment} <"), + format!("{segment}<"), + format!(":: {segment} <"), + format!("::{segment}<"), + ]; + let start = patterns + .iter() + .filter_map(|pattern| rust_type.find(pattern).map(|index| index + pattern.len())) + .next()?; + let end = matching_generic_end(rust_type, start)?; + Some(rust_type[start..end].trim().to_owned()) +} + +fn matching_generic_end(text: &str, start: usize) -> Option { + let mut depth = 1usize; + for (offset, ch) in text[start..].char_indices() { + match ch { + '<' => depth += 1, + '>' => { + depth -= 1; + if depth == 0 { + return Some(start + offset); + } + } + _ => {} + } + } + None +} + +fn split_top_level_commas(text: &str) -> Vec { + let mut parts = Vec::new(); + let mut depth = 0usize; + let mut start = 0usize; + + for (index, ch) in text.char_indices() { + match ch { + '<' => depth += 1, + '>' => depth = depth.saturating_sub(1), + ',' if depth == 0 => { + parts.push(text[start..index].trim().to_owned()); + start = index + ch.len_utf8(); + } + _ => {} + } + } + + parts.push(text[start..].trim().to_owned()); + parts +} + +fn type_last_segment(rust_type: &str) -> Option<&str> { + let head = rust_type.split('<').next().unwrap_or(rust_type).trim(); + head.split("::").last().map(str::trim) +} + +fn strip_angle_binding(value: &str) -> &str { + value + .strip_prefix('<') + .and_then(|value| value.strip_suffix('>')) + .unwrap_or(value) +} + +fn type_to_string(ty: &Type) -> String { + ty.to_token_stream().to_string() +} + +#[allow(dead_code)] +fn _normalize_path(path: PathBuf) -> String { + path.display().to_string() +} + +#[cfg(test)] +mod tests { + use super::*; + + fn write(dir: &Path, relative: &str, contents: &str) { + let path = dir.join(relative); + fs::create_dir_all(path.parent().unwrap()).unwrap(); + fs::write(path, contents).unwrap(); + } + + #[test] + fn discovers_json_routes_and_auth_guards() { + let dir = tempfile::tempdir().unwrap(); + write( + dir.path(), + "src/tasks/routes.rs", + r#" + use comet::cloudflare::D1; + use comet_auth::{AuthSession, AuthorizedSession}; + use rocket::serde::json::Json; + + pub struct DB; + pub struct TaskWritePolicy; + pub struct Task; + pub struct NewTask; + pub struct ApiError; + pub type ApiResult = Result; + + #[get("/tasks")] + pub async fn list_tasks(session: AuthSession, db: D1) -> ApiResult>> { + todo!() + } + + #[post("/tasks", data = "")] + pub async fn create_task(new_task: Json, session: AuthSession) -> ApiResult> { + todo!() + } + + #[post("/tasks//complete")] + pub async fn complete_task(id: i32, session: AuthorizedSession) -> ApiResult> { + todo!() + } + "#, + ); + + let manifest = discover_manifest(dir.path()).unwrap(); + + assert_eq!(manifest.version, MANIFEST_VERSION); + assert_eq!(manifest.routes.len(), 3); + assert_eq!(manifest.routes[0].name, "complete_task"); + assert_eq!(manifest.routes[0].path_params[0].name, "id"); + assert_eq!(manifest.routes[0].path_params[0].rust_type, "i32"); + assert_eq!( + manifest.routes[0].auth, + RpcAuth::Authorized { + policy: Some("TaskWritePolicy".to_owned()), + roles: Vec::new(), + permissions: Vec::new(), + scopes: Vec::new(), + resource: None, + mode: AuthMode::All, + } + ); + assert_eq!(manifest.routes[1].body, Some("NewTask".to_owned())); + assert_eq!(manifest.routes[1].response, Some("Task".to_owned())); + assert_eq!(manifest.routes[2].response, Some("Vec < Task >".to_owned())); + assert_eq!(manifest.routes[2].auth, RpcAuth::Required); + } + + #[test] + fn parses_requires_auth_macro_metadata() { + let dir = tempfile::tempdir().unwrap(); + write( + dir.path(), + "src/demo/routes.rs", + r#" + #[comet_auth::requires_auth(any(role = "admin", permission = "tasks:review"), resource = "demo")] + #[get("/private/reviewer")] + pub async fn private_reviewer() -> &'static str { + "reviewer" + } + "#, + ); + + let manifest = discover_manifest(dir.path()).unwrap(); + + assert_eq!( + manifest.routes[0].auth, + RpcAuth::Authorized { + policy: None, + roles: vec!["admin".to_owned()], + permissions: vec!["tasks:review".to_owned()], + scopes: Vec::new(), + resource: Some("demo".to_owned()), + mode: AuthMode::Any, + } + ); + assert_eq!(manifest.routes[0].support, RpcRouteSupport::Unsupported); + } + + #[test] + fn marks_raw_routes() { + let dir = tempfile::tempdir().unwrap(); + write( + dir.path(), + "src/assets/routes.rs", + r#" + use std::path::PathBuf; + use rocket::data::Capped; + use rocket::http::Status; + + #[put("/assets/", data = "")] + pub async fn put_asset(key: PathBuf, body: Capped>) -> Result { + todo!() + } + "#, + ); + + let manifest = discover_manifest(dir.path()).unwrap(); + + assert_eq!(manifest.routes[0].support, RpcRouteSupport::Raw); + assert_eq!(manifest.routes[0].path_params[0].name, "key"); + assert!(manifest.routes[0].path_params[0].variadic); + } + + #[test] + fn applies_mount_prefixes_from_routes_macro() { + let dir = tempfile::tempdir().unwrap(); + write( + dir.path(), + "src/app.rs", + r#" + use crate::tasks::routes::list_tasks; + + pub fn rocket() { + rocket::build().mount("/api/v1", routes![list_tasks]); + } + "#, + ); + write( + dir.path(), + "src/tasks/routes.rs", + r#" + use rocket::serde::json::Json; + + pub struct Task; + + #[get("/tasks")] + pub async fn list_tasks() -> Json> { + todo!() + } + "#, + ); + + let manifest = discover_manifest(dir.path()).unwrap(); + + assert_eq!(manifest.routes[0].path, "/api/v1/tasks"); + assert!(manifest.routes[0].warnings.is_empty()); + } + + #[test] + fn warns_when_route_has_multiple_mount_prefixes() { + let dir = tempfile::tempdir().unwrap(); + write( + dir.path(), + "src/app.rs", + r#" + use crate::tasks::routes::list_tasks; + + pub fn rocket() { + rocket::build() + .mount("/api", routes![list_tasks]) + .mount("/internal", routes![list_tasks]); + } + "#, + ); + write( + dir.path(), + "src/tasks/routes.rs", + r#" + use rocket::serde::json::Json; + + pub struct Task; + + #[get("/tasks")] + pub async fn list_tasks() -> Json> { + todo!() + } + "#, + ); + + let manifest = discover_manifest(dir.path()).unwrap(); + + assert_eq!(manifest.routes[0].path, "/tasks"); + assert!( + manifest.routes[0] + .warnings + .iter() + .any(|warning| warning.contains("multiple prefixes")) + ); + } + + #[test] + fn generates_typescript_client_for_json_routes() { + let manifest = RpcManifest { + version: MANIFEST_VERSION, + routes: vec![ + RpcRoute { + name: "get_task".to_owned(), + module_path: vec!["tasks".to_owned(), "routes".to_owned()], + source: "src/tasks/routes.rs".to_owned(), + method: "GET".to_owned(), + path: "/api/tasks/".to_owned(), + data_param: None, + path_params: vec![RpcParam { + name: "id".to_owned(), + rust_type: "i32".to_owned(), + variadic: false, + }], + query_params: Vec::new(), + body: None, + response: Some("Task".to_owned()), + error: None, + auth: RpcAuth::Required, + support: RpcRouteSupport::Json, + warnings: Vec::new(), + }, + RpcRoute { + name: "put_asset".to_owned(), + module_path: vec!["assets".to_owned(), "routes".to_owned()], + source: "src/assets/routes.rs".to_owned(), + method: "PUT".to_owned(), + path: "/assets/".to_owned(), + data_param: Some("body".to_owned()), + path_params: vec![RpcParam { + name: "key".to_owned(), + rust_type: "PathBuf".to_owned(), + variadic: true, + }], + query_params: Vec::new(), + body: None, + response: None, + error: None, + auth: RpcAuth::None, + support: RpcRouteSupport::Raw, + warnings: Vec::new(), + }, + ], + }; + + let mut types = TsTypes::default(); + types.models.insert( + "Task".to_owned(), + TsModel { + name: "Task".to_owned(), + fields: vec![TsModelField { + name: "id".to_owned(), + rust_type: "i32".to_owned(), + ts_type: "number".to_owned(), + optional: false, + }], + }, + ); + + let ts = generate_typescript_client_with_types(&manifest, &types); + + assert!(ts.contains("export interface Task {\n id: number;\n}")); + assert!(ts.contains("async getTask(id: number): Promise")); + assert!(ts.contains("`/api/tasks/${encodePathValue(id)}`")); + assert!(ts.contains("this.request(\"GET\"")); + assert!(ts.contains("const payload = parseJsonResponse(text, response.ok);")); + assert!(ts.contains("if (ok) throw new SyntaxError")); + assert!(!ts.contains("putAsset")); + } + + #[test] + fn discovers_and_generates_query_params() { + let dir = tempfile::tempdir().unwrap(); + write( + dir.path(), + "src/tasks/routes.rs", + r#" + use rocket::serde::json::Json; + + pub struct Task; + + #[get("/tasks?&")] + pub async fn list_tasks(done: bool, page: i32) -> Json> { + todo!() + } + "#, + ); + + let manifest = discover_manifest(dir.path()).unwrap(); + let route = &manifest.routes[0]; + assert_eq!(route.path, "/tasks"); + assert_eq!( + route.query_params, + vec![ + RpcParam { + name: "done".to_owned(), + rust_type: "bool".to_owned(), + variadic: false, + }, + RpcParam { + name: "page".to_owned(), + rust_type: "i32".to_owned(), + variadic: false, + }, + ] + ); + + let types = TsTypes::default(); + let ts = generate_typescript_client_with_types(&manifest, &types); + let dart = generate_dart_client_with_types(&manifest, &types); + let rust = generate_rust_client_with_types(&manifest, &types); + + assert!(ts.contains("async listTasks(done: boolean, page: number)")); + assert!(ts.contains("`/tasks?done=${encodeURIComponent(String(done))}&page=${encodeURIComponent(String(page))}`")); + assert!(dart.contains("Future> listTasks(bool done, int page) async")); + assert!(dart.contains("'/tasks?done=${Uri.encodeQueryComponent('$done')}&page=${Uri.encodeQueryComponent('$page')}'")); + assert!(rust.contains("pub async fn list_tasks(&self, done: bool, page: i32)")); + assert!(rust.contains("\"/tasks?done={}&page={}\"")); + } + + #[test] + fn serializes_manifest_with_support_auth_and_query_params() { + let manifest = RpcManifest { + version: MANIFEST_VERSION, + routes: vec![RpcRoute { + name: "list_tasks".to_owned(), + module_path: vec!["tasks".to_owned(), "routes".to_owned()], + source: "src/tasks/routes.rs".to_owned(), + method: "GET".to_owned(), + path: "/tasks".to_owned(), + data_param: None, + path_params: Vec::new(), + query_params: vec![RpcParam { + name: "done".to_owned(), + rust_type: "bool".to_owned(), + variadic: false, + }], + body: None, + response: Some("Vec < Task >".to_owned()), + error: Some("ApiError".to_owned()), + auth: RpcAuth::Optional, + support: RpcRouteSupport::Json, + warnings: Vec::new(), + }], + }; + + let json = serde_json::to_value(&manifest).unwrap(); + + assert_eq!(json["version"], MANIFEST_VERSION); + assert_eq!(json["routes"][0]["support"], "json"); + assert_eq!(json["routes"][0]["auth"]["kind"], "optional"); + assert_eq!(json["routes"][0]["query_params"][0]["name"], "done"); + assert_eq!(json["routes"][0]["error"], "ApiError"); + } + + #[test] + fn resolves_local_result_alias_error_type() { + let dir = tempfile::tempdir().unwrap(); + write( + dir.path(), + "src/tasks/routes.rs", + r#" + use rocket::serde::json::Json; + + pub struct Task; + pub struct RouteError; + pub type RouteResult = Result; + + #[get("/tasks/")] + pub async fn get_task(id: i32) -> RouteResult> { + todo!() + } + "#, + ); + + let manifest = discover_manifest(dir.path()).unwrap(); + + assert_eq!(manifest.routes[0].response, Some("Task".to_owned())); + assert_eq!(manifest.routes[0].error, Some("RouteError".to_owned())); + } + + #[test] + fn discovers_typescript_models_for_used_structs() { + let dir = tempfile::tempdir().unwrap(); + write( + dir.path(), + "src/tasks/routes.rs", + r#" + use rocket::serde::json::Json; + + #[derive(serde::Serialize)] + #[serde(rename_all = "camelCase")] + pub struct Task { + pub id: i32, + #[serde(rename = "taskTitle")] + pub title: String, + pub done: bool, + pub archived_at: Option, + pub status: TaskStatus, + private_note: String, + } + + #[derive(serde::Serialize)] + #[serde(rename_all = "snake_case")] + pub enum TaskStatus { + InProgress, + Done, + #[serde(rename = "blocked_custom")] + Blocked, + } + + #[get("/tasks/")] + pub async fn get_task(id: i32) -> Json { + todo!() + } + "#, + ); + + let manifest = discover_manifest(dir.path()).unwrap(); + let types = discover_typescript_types(dir.path(), &manifest).unwrap(); + let ts = generate_typescript_client_with_types(&manifest, &types); + + assert!(ts.contains("export interface Task {")); + assert!(ts.contains(" id: number;")); + assert!(ts.contains(" taskTitle: string;")); + assert!(ts.contains(" done: boolean;")); + assert!(ts.contains(" archivedAt?: string | null;")); + assert!(ts.contains(" status: TaskStatus;")); + assert!( + ts.contains( + "export type TaskStatus = \"in_progress\" | \"done\" | \"blocked_custom\";" + ) + ); + assert!(!ts.contains("private_note")); + } + + #[test] + fn falls_back_when_enum_schema_is_not_unit_only() { + let dir = tempfile::tempdir().unwrap(); + write( + dir.path(), + "src/tasks/routes.rs", + r#" + use rocket::serde::json::Json; + + pub struct Task { + pub status: TaskStatus, + } + + pub enum TaskStatus { + Done, + Failed(String), + } + + #[get("/tasks/")] + pub async fn get_task(id: i32) -> Json { + todo!() + } + "#, + ); + + let manifest = discover_manifest(dir.path()).unwrap(); + let types = discover_typescript_types(dir.path(), &manifest).unwrap(); + let ts = generate_typescript_client_with_types(&manifest, &types); + let dart = generate_dart_client_with_types(&manifest, &types); + let rust = generate_rust_client_with_types(&manifest, &types); + + assert!(!types.enums.contains_key("TaskStatus")); + assert!(ts.contains("export type TaskStatus = unknown;")); + assert!(dart.contains("typedef TaskStatus = Object?;")); + assert!(rust.contains("pub type TaskStatus = serde_json::Value;")); + } + + #[test] + fn generates_dart_client_for_json_routes() { + let manifest = RpcManifest { + version: MANIFEST_VERSION, + routes: vec![RpcRoute { + name: "create_task".to_owned(), + module_path: vec!["tasks".to_owned(), "routes".to_owned()], + source: "src/tasks/routes.rs".to_owned(), + method: "POST".to_owned(), + path: "/tasks".to_owned(), + data_param: Some("new_task".to_owned()), + path_params: Vec::new(), + query_params: Vec::new(), + body: Some("NewTask".to_owned()), + response: Some("Task".to_owned()), + error: None, + auth: RpcAuth::Required, + support: RpcRouteSupport::Json, + warnings: Vec::new(), + }], + }; + let mut types = TsTypes::default(); + types.models.insert( + "NewTask".to_owned(), + TsModel { + name: "NewTask".to_owned(), + fields: vec![TsModelField { + name: "title".to_owned(), + rust_type: "String".to_owned(), + ts_type: "string".to_owned(), + optional: false, + }], + }, + ); + types.models.insert( + "Task".to_owned(), + TsModel { + name: "Task".to_owned(), + fields: vec![ + TsModelField { + name: "id".to_owned(), + rust_type: "i32".to_owned(), + ts_type: "number".to_owned(), + optional: false, + }, + TsModelField { + name: "status".to_owned(), + rust_type: "TaskStatus".to_owned(), + ts_type: "TaskStatus".to_owned(), + optional: false, + }, + ], + }, + ); + types.enums.insert( + "TaskStatus".to_owned(), + TsEnum { + name: "TaskStatus".to_owned(), + variants: vec!["in_progress".to_owned(), "done".to_owned()], + }, + ); + + let dart = generate_dart_client_with_types(&manifest, &types); + + assert!(dart.contains("class NewTask {")); + assert!(dart.contains("final String title;")); + assert!(dart.contains("enum TaskStatus {")); + assert!(dart.contains("in_progress")); + assert!(dart.contains("Future createTask(NewTask new_task) async")); + assert!(dart.contains("return _request('POST', '/tasks', new_task, true")); + assert!(dart.contains("Task.fromJson(value)")); + assert!(dart.contains("final payload = _decodeJsonResponse(text, ok);")); + assert!(dart.contains("if (ok) rethrow;")); + } + + #[test] + fn generates_rust_client_for_json_routes() { + let manifest = RpcManifest { + version: MANIFEST_VERSION, + routes: vec![RpcRoute { + name: "create_task".to_owned(), + module_path: vec!["tasks".to_owned(), "routes".to_owned()], + source: "src/tasks/routes.rs".to_owned(), + method: "POST".to_owned(), + path: "/tasks".to_owned(), + data_param: Some("new_task".to_owned()), + path_params: Vec::new(), + query_params: Vec::new(), + body: Some("NewTask".to_owned()), + response: Some("Task".to_owned()), + error: None, + auth: RpcAuth::Required, + support: RpcRouteSupport::Json, + warnings: Vec::new(), + }], + }; + let mut types = TsTypes::default(); + types.models.insert( + "NewTask".to_owned(), + TsModel { + name: "NewTask".to_owned(), + fields: vec![TsModelField { + name: "title".to_owned(), + rust_type: "String".to_owned(), + ts_type: "string".to_owned(), + optional: false, + }], + }, + ); + types.models.insert( + "Task".to_owned(), + TsModel { + name: "Task".to_owned(), + fields: vec![TsModelField { + name: "status".to_owned(), + rust_type: "TaskStatus".to_owned(), + ts_type: "TaskStatus".to_owned(), + optional: false, + }], + }, + ); + types.enums.insert( + "TaskStatus".to_owned(), + TsEnum { + name: "TaskStatus".to_owned(), + variants: vec!["in_progress".to_owned(), "done".to_owned()], + }, + ); + + let rust = generate_rust_client_with_types(&manifest, &types); + + assert!(rust.contains("pub struct NewTask")); + assert!(rust.contains("pub title: String,")); + assert!(rust.contains("pub enum TaskStatus")); + assert!(rust.contains("#[serde(rename = \"in_progress\")]")); + assert!(rust.contains( + "pub async fn create_task(&self, new_task: &NewTask) -> Result" + )); + assert!(rust.contains("self.request(reqwest::Method::POST")); + assert!(rust.contains("Json(#[from] serde_json::Error),")); + assert!(rust.contains("let body = response.bytes().await?;")); + assert!(rust.contains("serde_json::from_str(\"null\")?")); + } + + #[test] + fn resolves_required_authorization_policy_from_target_crate() { + let dir = tempfile::tempdir().unwrap(); + let workspace = Path::new(env!("CARGO_MANIFEST_DIR")) + .parent() + .unwrap() + .to_path_buf(); + let comet_auth_path = workspace.join("comet-auth"); + let rocket_path = workspace.join("vendor/rocket/core/lib"); + + write( + dir.path(), + "Cargo.toml", + &format!( + r#" + [package] + name = "rpc-policy-fixture" + version = "0.0.0" + edition = "2021" + + [dependencies] + comet-auth = {{ path = "{}", default-features = false }} + rocket = {{ path = "{}", default-features = false, features = ["json"] }} + "#, + comet_auth_path.display(), + rocket_path.display(), + ), + ); + write(dir.path(), "src/lib.rs", "pub mod routes;\n"); + write( + dir.path(), + "src/routes.rs", + r#" + use comet_auth::{ + AuthorizationMode, AuthorizationRequirement, AuthorizedSession, + RequiredAuthorization, + }; + + pub struct WritePolicy; + + impl RequiredAuthorization for WritePolicy { + const REQUIREMENT: AuthorizationRequirement = + AuthorizationRequirement::with_mode_and_resource( + AuthorizationMode::Any, + &["admin"], + &["tasks:write"], + &["tasks:review"], + Some("tasks"), + ); + } + + #[rocket::post("/write")] + pub async fn write(_session: AuthorizedSession) -> &'static str { + "ok" + } + "#, + ); + + let manifest = discover_manifest(dir.path()).unwrap(); + let RpcAuth::Authorized { + roles, + permissions, + scopes, + resource, + mode, + .. + } = &manifest.routes[0].auth + else { + panic!("expected authorized route"); + }; + + assert_eq!(roles, &vec!["admin".to_owned()]); + assert_eq!(permissions, &vec!["tasks:write".to_owned()]); + assert_eq!(scopes, &vec!["tasks:review".to_owned()]); + assert_eq!(resource, &Some("tasks".to_owned())); + assert_eq!(mode, &AuthMode::Any); + } +} diff --git a/docs-site/content/docs/comet-cli.en.mdx b/docs-site/content/docs/comet-cli.en.mdx index dd4a674..3597c3d 100644 --- a/docs-site/content/docs/comet-cli.en.mdx +++ b/docs-site/content/docs/comet-cli.en.mdx @@ -4,7 +4,7 @@ description: Full command reference for the comet binary — new, generate, migr --- `comet-cli` is a Rust binary (command `comet`) that lives in `comet-cli/`, -outside the main workspace — just like `comet-macros`. It does four things: +outside the main workspace — just like `comet-macros`. It does seven things: - `comet new` — creates a Comet + Nebula + Cloudflare Worker project. - `comet generate entity|route` — adds `#[derive(Entity)]` structs and CRUD @@ -12,6 +12,9 @@ outside the main workspace — just like `comet-macros`. It does four things: - `comet migrate init|generate|status` — drives migration generation from the entities' real, compiled metadata. - `comet auth init` — adds authentication migrations and optional RBAC. +- `comet rls status` — inspects Nebula row-level-security coverage. +- `comet rpc manifest|generate` — inspects Rocket routes and emits typed RPC + client code. - `comet test unit|integration|perf|all` — runs the project's test/release gate. @@ -222,6 +225,32 @@ baseline snapshot exists, either "up to date", the pending SQL statements, or the list of blockers. Always exits successfully — it's a report, not a gate. +## `comet rpc` + +``` +comet rpc manifest [--path ] [--out ] +comet rpc generate --lang [--path ] [--out ] +``` + +`manifest` walks the Rocket route functions under `src/` and emits JSON with +the function name, source file, method, mounted path when it can infer +`rocket.mount(...)`, path/query parameters, `Json` body/response types, +auth metadata, support classification, and warnings. + +`generate` emits a typed client for the JSON-supported routes. It discovers +referenced public structs and unit enums under `src/`, generates matching +client-side type declarations, and omits raw or unsupported routes. + +- `--lang ts`: emits a `fetch`-based TypeScript client. +- `--lang dart`: emits a Dart client using `package:http/http.dart`. +- `--lang rust`: emits a Rust client using `reqwest`, `serde`, + `serde_json`, `thiserror`, and `percent-encoding`. + +Authenticated routes are detected from `AuthSession`, `AuthorizedSession`, +and `#[comet_auth::requires_auth(...)]`. Policy guards can be compiled in a +temporary crate so their roles, permissions, scopes, resource, and mode are +included in the manifest. + ## `comet test` ``` diff --git a/docs-site/content/docs/comet-cli.mdx b/docs-site/content/docs/comet-cli.mdx index 486bab2..c90b0a7 100644 --- a/docs-site/content/docs/comet-cli.mdx +++ b/docs-site/content/docs/comet-cli.mdx @@ -5,7 +5,7 @@ description: Referência completa dos comandos do binário comet — new, genera `comet-cli` é um binário Rust (comando `comet`) que fica em `comet-cli/`, fora do workspace principal — assim como `comet-macros`. Ele -faz quatro coisas: +faz sete coisas: - `comet new` — cria um projeto Comet + Nebula + Cloudflare Worker. - `comet generate entity|route` — adiciona structs `#[derive(Entity)]` e @@ -14,6 +14,10 @@ faz quatro coisas: partir da metadata real e compilada das entidades. - `comet auth init` — adiciona migrations de autenticação e, opcionalmente, RBAC. +- `comet rls status` — inspeciona a cobertura de row-level security das + entidades Nebula. +- `comet rpc manifest|generate` — inspeciona rotas Rocket e emite código de + cliente RPC tipado. - `comet test unit|integration|perf|all` — roda o gate de testes/release do projeto. @@ -223,6 +227,33 @@ atual e, se houver um snapshot base, "up to date", os statements SQL pendentes, ou a lista de bloqueadores. Sempre sai com sucesso — é um relatório, não um gate. +## `comet rpc` + +``` +comet rpc manifest [--path ] [--out ] +comet rpc generate --lang [--path ] [--out ] +``` + +`manifest` percorre as funções de rota Rocket sob `src/` e emite JSON com +nome da função, arquivo de origem, método, path montado quando consegue +inferir `rocket.mount(...)`, parâmetros de path/query, tipos `Json` de +body/resposta, metadata de auth, classificação de suporte e warnings. + +`generate` emite um cliente tipado para as rotas com suporte JSON. Ele +descobre structs públicas e enums unitários referenciados sob `src/`, gera +as declarações de tipo correspondentes no cliente, e omite rotas raw ou sem +suporte. + +- `--lang ts`: emite um cliente TypeScript baseado em `fetch`. +- `--lang dart`: emite um cliente Dart usando `package:http/http.dart`. +- `--lang rust`: emite um cliente Rust usando `reqwest`, `serde`, + `serde_json`, `thiserror` e `percent-encoding`. + +Rotas autenticadas são detectadas a partir de `AuthSession`, +`AuthorizedSession` e `#[comet_auth::requires_auth(...)]`. Guards de +policy podem ser compilados em um crate temporário para incluir roles, +permissions, scopes, resource e modo no manifesto. + ## `comet test` ``` diff --git a/docs-site/content/docs/index.en.mdx b/docs-site/content/docs/index.en.mdx index 8a26ac6..1b93e0d 100644 --- a/docs-site/content/docs/index.en.mdx +++ b/docs-site/content/docs/index.en.mdx @@ -63,6 +63,10 @@ pub async fn main(req: Request, env: Env, ctx: Context) -> Result { deterministic SQL builders, explicit relationships, and safe migration generation. + + Route inspection and generated TypeScript, Dart, and Rust clients for + Rocket routes with JSON request/response contracts. + A binary (`comet`) that scaffolds new projects, generates entities and CRUD routes, drives migration generation, and runs your project's test diff --git a/docs-site/content/docs/index.mdx b/docs-site/content/docs/index.mdx index c3e490e..a150ab6 100644 --- a/docs-site/content/docs/index.mdx +++ b/docs-site/content/docs/index.mdx @@ -63,6 +63,10 @@ pub async fn main(req: Request, env: Env, ctx: Context) -> Result { tipadas, builders de SQL determinísticos, relacionamentos explícitos e geração segura de migrations. + + Inspeção de rotas e clientes TypeScript, Dart e Rust gerados para rotas + Rocket com contratos JSON de request/response. + Um binário (`comet`) que cria projetos novos, gera entidades e rotas CRUD, conduz a geração de migrations e roda o gate de testes do diff --git a/docs-site/content/docs/meta.json b/docs-site/content/docs/meta.json index a0eb0e5..a266812 100644 --- a/docs-site/content/docs/meta.json +++ b/docs-site/content/docs/meta.json @@ -6,6 +6,7 @@ "cloudflare-adapter", "nebula-orm", "auth", + "rpc", "comet-cli", "example-walkthrough" ] diff --git a/docs-site/content/docs/rpc.en.mdx b/docs-site/content/docs/rpc.en.mdx new file mode 100644 index 0000000..7016aa9 --- /dev/null +++ b/docs-site/content/docs/rpc.en.mdx @@ -0,0 +1,135 @@ +--- +title: Typed RPC +description: Generate typed TypeScript, Dart, and Rust clients from Rocket routes. +--- + +Comet RPC is a `comet-cli` feature that inspects Rocket route functions and +generates client code for routes with JSON request/response contracts. It does +not add a new server runtime protocol: your server remains ordinary Rocket, +and the generated client calls the same HTTP routes. + +## Route Shape + +RPC generation supports routes whose body and response are represented with +`rocket::serde::json::Json`: + +```rust +use rocket::serde::json::Json; + +#[derive(serde::Serialize, serde::Deserialize)] +pub struct NewTask { + pub title: String, +} + +#[derive(serde::Serialize, serde::Deserialize)] +pub struct Task { + pub id: i32, + pub title: String, + pub done: bool, +} + +#[post("/tasks", data = "")] +pub async fn create_task(new_task: Json) -> ApiResult> { + todo!() +} + +#[get("/tasks/")] +pub async fn get_task(id: i32) -> ApiResult> { + todo!() +} +``` + +The CLI extracts: + +- path parameters from route placeholders like `` and ``; +- query parameters from route placeholders like `?&`; +- request bodies from `data = ""` arguments typed as `Json`; +- responses from `Json`, `Result, E>`, `ApiResult>`, and + local `Result` aliases. + +## Manifest + +Use `manifest` to inspect what the CLI sees before generating code: + +```sh +comet rpc manifest --path . --out rpc-manifest.json +``` + +The manifest includes route names, source files, HTTP methods, mounted paths +when `rocket.mount(...)` can be inferred, path/query params, body/response +types, auth metadata, support classification, and warnings. + +Support classifications are: + +- `json`: the route can be included in typed client generation. +- `raw`: the route is recognized, but uses raw bodies, streams, WebSockets, + R2 objects, status responders, or other non-JSON shapes. +- `unsupported`: the route is visible, but there is not enough JSON contract + information to generate a typed call. + +## Generate Clients + +Generate a client with: + +```sh +comet rpc generate --lang ts --path . --out src/comet-rpc.ts +comet rpc generate --lang dart --path . --out lib/comet_rpc.dart +comet rpc generate --lang rust --path . --out src/comet_rpc.rs +``` + +Only `json` routes are emitted. Raw and unsupported routes stay out of the +generated client instead of being exposed with misleading types. + +The generated clients use bearer tokens for authenticated routes: + +- TypeScript: pass a `TokenProvider` to `new CometClient(baseUrl, tokenProvider)`. +- Dart: pass `tokenProvider:` to `CometClient`. +- Rust: call `.with_bearer_token(token)` on `CometClient`. + +The server still enforces authentication and authorization. The client only +sends a bearer token when the manifest marks the route as authenticated. + +## React Native Example + +There is a React Native example at +[`examples/react-native-rpc`](https://github.com/viniciusamelio/comet/tree/main/examples/react-native-rpc) +that consumes the TypeScript client generated from +`examples/cloudflare-worker`. + +The client used by the app is produced with: + +```sh +cargo run -p comet-cli -- rpc generate \ + --lang ts \ + --path examples/cloudflare-worker \ + --out examples/react-native-rpc/src/comet-rpc.ts +``` + +The example screen lets you configure the API URL, provide a bearer token, +list tasks with `listTasks()`, create tasks with `createTask()`, and complete +tasks with `completeTask()`. + +## Generated Types + +The generator discovers referenced public structs and unit enums under `src/`. +It supports public named fields, `Option`, `Vec`, nested custom types, +`#[serde(rename = "...")]`, `#[serde(skip)]`, and common +`#[serde(rename_all = "...")]` enum cases. + +For client dependencies: + +- TypeScript uses `fetch`. +- Dart uses `package:http/http.dart`. +- Rust uses `reqwest`, `serde`, `serde_json`, `thiserror`, and + `percent-encoding`. + +## Limits + +RPC generation is intentionally conservative today: + +- request bodies must be `Json`; +- tuple structs, generic DTO specialization, and enums with payloads are not + modeled; +- type aliases for DTOs are not expanded; +- streaming, WebSocket, R2, `Status`, and raw byte routes are detected but not + generated as typed client methods yet. diff --git a/docs-site/content/docs/rpc.mdx b/docs-site/content/docs/rpc.mdx new file mode 100644 index 0000000..adc7ef9 --- /dev/null +++ b/docs-site/content/docs/rpc.mdx @@ -0,0 +1,136 @@ +--- +title: RPC tipado +description: Gere clientes TypeScript, Dart e Rust tipados a partir de rotas Rocket. +--- + +O RPC do Comet é uma funcionalidade do `comet-cli` que inspeciona funções de +rota Rocket e gera código de cliente para rotas com contratos JSON de +request/response. Ele não adiciona um protocolo novo no servidor: o servidor +continua sendo Rocket comum, e o cliente gerado chama as mesmas rotas HTTP. + +## Formato das rotas + +A geração RPC suporta rotas cujo body e resposta são representados com +`rocket::serde::json::Json`: + +```rust +use rocket::serde::json::Json; + +#[derive(serde::Serialize, serde::Deserialize)] +pub struct NewTask { + pub title: String, +} + +#[derive(serde::Serialize, serde::Deserialize)] +pub struct Task { + pub id: i32, + pub title: String, + pub done: bool, +} + +#[post("/tasks", data = "")] +pub async fn create_task(new_task: Json) -> ApiResult> { + todo!() +} + +#[get("/tasks/")] +pub async fn get_task(id: i32) -> ApiResult> { + todo!() +} +``` + +O CLI extrai: + +- parâmetros de path de placeholders como `` e ``; +- parâmetros de query de placeholders como `?&`; +- bodies a partir de argumentos `data = ""` tipados como `Json`; +- respostas a partir de `Json`, `Result, E>`, + `ApiResult>` e aliases locais de `Result`. + +## Manifesto + +Use `manifest` para inspecionar o que o CLI enxerga antes de gerar código: + +```sh +comet rpc manifest --path . --out rpc-manifest.json +``` + +O manifesto inclui nomes de rotas, arquivos de origem, métodos HTTP, paths +montados quando `rocket.mount(...)` pode ser inferido, parâmetros de +path/query, tipos de body/resposta, metadata de auth, classificação de +suporte e warnings. + +As classificações de suporte são: + +- `json`: a rota pode entrar na geração de cliente tipado. +- `raw`: a rota foi reconhecida, mas usa bodies raw, streams, WebSockets, + objetos R2, responders de status ou outros formatos não JSON. +- `unsupported`: a rota é visível, mas não há contrato JSON suficiente para + gerar uma chamada tipada. + +## Gerar clientes + +Gere um cliente com: + +```sh +comet rpc generate --lang ts --path . --out src/comet-rpc.ts +comet rpc generate --lang dart --path . --out lib/comet_rpc.dart +comet rpc generate --lang rust --path . --out src/comet_rpc.rs +``` + +Apenas rotas `json` são emitidas. Rotas raw e sem suporte ficam fora do +cliente gerado, em vez de aparecerem com tipos enganosos. + +Os clientes gerados usam bearer token para rotas autenticadas: + +- TypeScript: passe um `TokenProvider` para `new CometClient(baseUrl, tokenProvider)`. +- Dart: passe `tokenProvider:` para `CometClient`. +- Rust: chame `.with_bearer_token(token)` em `CometClient`. + +O servidor continua aplicando autenticação e autorização. O cliente só envia +um bearer token quando o manifesto marca a rota como autenticada. + +## Exemplo React Native + +Há um exemplo React Native em +[`examples/react-native-rpc`](https://github.com/viniciusamelio/comet/tree/main/examples/react-native-rpc) +que consome o cliente TypeScript gerado a partir de +`examples/cloudflare-worker`. + +O cliente usado pelo app é produzido com: + +```sh +cargo run -p comet-cli -- rpc generate \ + --lang ts \ + --path examples/cloudflare-worker \ + --out examples/react-native-rpc/src/comet-rpc.ts +``` + +A tela do exemplo permite configurar a URL da API, informar um bearer token, +listar tarefas com `listTasks()`, criar tarefas com `createTask()` e concluir +tarefas com `completeTask()`. + +## Tipos gerados + +O gerador descobre structs públicas e enums unitários referenciados sob +`src/`. Ele suporta campos públicos nomeados, `Option`, `Vec`, tipos +customizados aninhados, `#[serde(rename = "...")]`, `#[serde(skip)]` e casos +comuns de `#[serde(rename_all = "...")]` em enums. + +Dependências dos clientes: + +- TypeScript usa `fetch`. +- Dart usa `package:http/http.dart`. +- Rust usa `reqwest`, `serde`, `serde_json`, `thiserror` e + `percent-encoding`. + +## Limites + +A geração RPC é conservadora hoje: + +- bodies precisam ser `Json`; +- tuple structs, especialização de DTOs genéricos e enums com payload não são + modelados; +- aliases de tipo para DTOs não são expandidos; +- rotas de streaming, WebSocket, R2, `Status` e bytes raw são detectadas, mas + ainda não são geradas como métodos tipados no cliente. diff --git a/docs/comet-cli-roadmap.md b/docs/comet-cli-roadmap.md index 062e62c..5e5fd2d 100644 --- a/docs/comet-cli-roadmap.md +++ b/docs/comet-cli-roadmap.md @@ -2,7 +2,8 @@ `comet-cli` (binary name `comet`) scaffolds Comet + Nebula + Cloudflare Worker projects, generates entities and CRUD routes, drives Nebula migration -generation, and orchestrates a project's test/release gate. It lives in this +generation, inspects routes for typed RPC clients, and orchestrates a +project's test/release gate. It lives in this repo, outside the Cargo workspace (same precedent as `comet-macros`), and releases in lockstep with the `comet` crate — a given `comet-cli` version always pairs with the `comet` core version it was built and tested against. @@ -146,6 +147,48 @@ generate` to act on what it shows. comet migrate status [--path ] ``` +### `comet rpc manifest` + +Discovers Rocket route functions under `src/` and emits a machine-readable +RPC manifest. + +``` +comet rpc manifest [--path ] [--out ] +``` + +The manifest includes each route's function name, module path, source file, +HTTP method, mounted path when it can be inferred from `rocket.mount(...)`, +path/query parameters, `Json` request/response types, auth metadata, and +support classification: + +- `json`: typed client generation supports this route. +- `raw`: route shape is recognized, but it uses non-JSON body/response data. +- `unsupported`: route is visible, but not currently safe to generate. + +Routes guarded by `AuthSession`/`AuthorizedSession` or +`#[comet_auth::requires_auth(...)]` are marked as authenticated. When a +project has a `Cargo.toml` and a `comet-auth` dependency, policy guards can +be compiled in a temporary crate so their roles, permissions, scopes, +resource, and authorization mode are included in the manifest. + +### `comet rpc generate` + +Generates a typed client for JSON-supported routes. + +``` +comet rpc generate --lang [--path ] [--out ] +``` + +The generator walks the same manifest, discovers referenced public structs +and unit enums under `src/`, and emits client-side type declarations plus a +`CometClient`. Raw and unsupported routes are intentionally omitted from the +generated client. + +Generated clients expect JSON HTTP endpoints and bearer-token auth when a +route is authenticated. The Rust client uses `reqwest`, `serde`, +`serde_json`, `thiserror`, and `percent-encoding`; the Dart client uses +`package:http/http.dart`; the TypeScript client only relies on `fetch`. + ### `comet test unit` / `integration` / `perf` / `all` ``` diff --git a/docs/rpc.md b/docs/rpc.md new file mode 100644 index 0000000..50502db --- /dev/null +++ b/docs/rpc.md @@ -0,0 +1,110 @@ +# Comet RPC + +O Comet RPC escaneia rotas Rocket/Comet existentes e gera um manifesto JSON +versionado. A partir desse manifesto, o CLI gera clientes tipados para +TypeScript, Dart e Rust. + +O RPC nao substitui as rotas HTTP. Guards, fairings e validacoes continuam no +servidor; o contrato gerado apenas reflete inputs, outputs e autenticacao para +clientes. + +## Comandos + +Gerar manifesto: + +```sh +comet rpc manifest --path examples/cloudflare-worker --out .comet-rpc.json +``` + +Gerar clientes: + +```sh +comet rpc generate --lang ts --path examples/cloudflare-worker --out src/comet-rpc.ts +comet rpc generate --lang dart --path examples/cloudflare-worker --out lib/comet_rpc.dart +comet rpc generate --lang rust --path examples/cloudflare-worker --out src/comet_rpc.rs +``` + +`--path` aponta para o projeto Rust alvo. Quando omitido, o diretorio atual e +usado. `--out` e opcional; sem ele, o output vai para stdout. + +## Rotas Elegiveis + +Uma rota entra como contrato JSON quando o scanner encontra um atributo Rocket +suportado e consegue inferir ao menos um contrato JSON de entrada ou saida. + +Atributos reconhecidos: + +- `#[get(...)]` +- `#[post(...)]` +- `#[put(...)]` +- `#[delete(...)]` +- `#[patch(...)]` + +Entradas e saidas reconhecidas no MVP: + +- path params como `/` e `/`; +- query params como `?&`; +- body `Json` vinculado a `data = ""`; +- response `Json`; +- response `Result, E>`; +- aliases simples como `type ApiResult = Result`. + +Rotas sem contrato JSON ainda aparecem no manifesto quando sao detectadas, mas +com `support: "raw"` ou `support: "unsupported"`. Isso evita que uma rota suma +silenciosamente da analise. + +## Autenticacao + +O manifesto representa autenticacao sem expor tokens ou segredos. + +Padroes detectados: + +- `AuthSession`: auth obrigatoria; +- `OptionalAuthSession`: auth opcional; +- `AuthorizedSession

`: auth obrigatoria com policy; +- `#[comet_auth::requires_auth(...)]`: roles, permissions, scopes, resource e + modo `any`/`all`; +- `RequiredAuthorization`: resolvido por introspeccao compilada quando o crate + alvo compila. + +Clientes gerados usam bearer token no MVP. TypeScript e Dart recebem um callback +opcional de token; Rust recebe token opcional no builder. Rotas publicas nao +tentam enviar token. + +## Tipos Suportados + +O scanner descobre structs publicos com campos nomeados e enums unitarios +simples usados direta ou indiretamente por rotas JSON. + +Mapeamentos comuns: + +- Rust primitives: `String`, `&str`, integers, floats, `bool`; +- wrappers: `Option` e `Vec`; +- structs locais publicos com campos publicos; +- enums unitarios locais; +- `serde(rename = "...")` em campos e variantes; +- `serde(rename_all = "...")` em structs e enums para `snake_case`, + `camelCase`, `PascalCase`, `kebab-case` e `SCREAMING_SNAKE_CASE`; +- `serde(skip)` em campos. + +Quando um tipo ainda nao tem schema estrutural descoberto, o gerador usa fallback +seguro: + +- TypeScript: `unknown`; +- Dart: `Object?`; +- Rust: `serde_json::Value`. + +## Limitacoes Iniciais + +Ainda nao ha suporte estrutural completo para: + +- forms e multipart; +- streaming, WebSocket, assets e bytes raw como cliente tipado; +- responders customizados sem `Json`; +- `serde(flatten)`; +- enums `untagged`, adjacently tagged ou com payload; +- query structs; +- reexports e imports complexos para resolver mounts em todos os casos. + +Nesses casos, o manifesto deve marcar a rota como `raw`/`unsupported`, emitir +warnings quando a inferencia for ambigua, ou gerar fallback de tipo no cliente. diff --git a/examples/react-native-rpc/.expo/README.md b/examples/react-native-rpc/.expo/README.md new file mode 100644 index 0000000..563b815 --- /dev/null +++ b/examples/react-native-rpc/.expo/README.md @@ -0,0 +1,14 @@ +> Why do I have a folder named ".expo" in my project? + +The ".expo" folder is created when an Expo project is started using "expo start" command. + +> What do the files contain? + +- "devices.json": contains information about devices that have recently opened this project. This is used to populate the "Development sessions" list in your development builds. +- "settings.json": contains the server configuration that is used to serve the application manifest. +- "dev/logs/": contains structured JSONL event logs from CLI commands (e.g. start.log, export.log). These are truncated on each run. + +> Should I commit the ".expo" folder? + +No, you should not share the ".expo" folder. It does not contain any information that is relevant for other developers working on the project, it is specific to your machine. +Upon project creation, the ".expo" folder is already added to your ".gitignore" file. diff --git a/examples/react-native-rpc/.expo/dev/logs/start.log b/examples/react-native-rpc/.expo/dev/logs/start.log new file mode 100644 index 0000000..3fc2fb8 --- /dev/null +++ b/examples/react-native-rpc/.expo/dev/logs/start.log @@ -0,0 +1,45 @@ +{"_e":"root:init","_t":1784047055737,"format":"v0-jsonl","version":"57.0.6"} +{"_e":"env:mode","_t":1784047055738,"nodeEnv":"development","babelEnv":"development","mode":"development"} +{"_e":"env:load","_t":1784047055739,"mode":"development","files":[],"env":{}} +{"_e":"devserver:start","_t":1784047055858,"mode":"development","web":true,"baseUrl":"","asyncRoutes":false,"routerRoot":"../../../app","serverComponents":false,"serverActions":false,"serverRendering":false,"apiRoutes":false,"exporting":false} +{"_e":"metro:config","_t":1784047056050,"serverRoot":"../../..","projectRoot":"../../..","exporting":false,"flags":{"autolinkingModuleResolution":false,"serverActions":false,"serverComponents":false,"reactCompiler":false,"optimizeGraph":false,"treeshaking":false,"logbox":false}} +{"_e":"expo:dev-tools-plugin:load","_t":1784047056184,"plugins":[]} +{"_e":"metro:instantiate","_t":1784047056530,"atlas":false,"workers":6,"host":"::","port":8081} +{"_e":"metro:bundling:started","_t":1784047080416,"id":"1","platform":"ios","environment":null,"entry":"index.ts"} +{"_e":"metro:bundling:progress","_t":1784047080417,"id":"1","progress":0,"total":1,"current":0} +{"_e":"metro:bundling:progress","_t":1784047080420,"id":"1","progress":0,"total":1,"current":0} +{"_e":"metro:bundling:progress","_t":1784047080796,"id":"1","progress":0.010000000000000002,"total":1,"current":1} +{"_e":"metro:bundling:progress","_t":1784047080897,"id":"1","progress":0.1111111111111111,"total":18,"current":6} +{"_e":"metro:bundling:progress","_t":1784047081000,"id":"1","progress":0.2025,"total":20,"current":9} +{"_e":"metro:bundling:progress","_t":1784047081102,"id":"1","progress":0.2025,"total":127,"current":14} +{"_e":"metro:bundling:progress","_t":1784047081203,"id":"1","progress":0.2025,"total":142,"current":21} +{"_e":"metro:bundling:progress","_t":1784047081303,"id":"1","progress":0.2025,"total":150,"current":26} +{"_e":"metro:bundling:progress","_t":1784047081404,"id":"1","progress":0.2025,"total":161,"current":29} +{"_e":"metro:bundling:progress","_t":1784047081505,"id":"1","progress":0.2025,"total":162,"current":31} +{"_e":"metro:bundling:progress","_t":1784047081606,"id":"1","progress":0.2025,"total":186,"current":35} +{"_e":"metro:bundling:progress","_t":1784047081706,"id":"1","progress":0.2025,"total":189,"current":38} +{"_e":"metro:bundling:progress","_t":1784047081806,"id":"1","progress":0.2025,"total":194,"current":45} +{"_e":"metro:bundling:progress","_t":1784047081907,"id":"1","progress":0.2025,"total":222,"current":64} +{"_e":"metro:bundling:progress","_t":1784047082007,"id":"1","progress":0.2025,"total":262,"current":100} +{"_e":"metro:bundling:progress","_t":1784047082108,"id":"1","progress":0.21204284314072813,"total":291,"current":134} +{"_e":"metro:bundling:progress","_t":1784047082208,"id":"1","progress":0.25636917160711425,"total":316,"current":160} +{"_e":"metro:bundling:progress","_t":1784047082311,"id":"1","progress":0.351554546166497,"total":339,"current":201} +{"_e":"metro:bundling:progress","_t":1784047082410,"id":"1","progress":0.4031242126480221,"total":378,"current":240} +{"_e":"metro:bundling:progress","_t":1784047082510,"id":"1","progress":0.4031242126480221,"total":455,"current":286} +{"_e":"metro:bundling:progress","_t":1784047082611,"id":"1","progress":0.4205983788799215,"total":478,"current":310} +{"_e":"metro:bundling:progress","_t":1784047082711,"id":"1","progress":0.4633836551023371,"total":498,"current":339} +{"_e":"metro:bundling:progress","_t":1784047082813,"id":"1","progress":0.5486968449931412,"total":513,"current":380} +{"_e":"metro:bundling:progress","_t":1784047082913,"id":"1","progress":0.6015997665448259,"total":517,"current":401} +{"_e":"metro:bundling:progress","_t":1784047083013,"id":"1","progress":0.6388835588693049,"total":573,"current":458} +{"_e":"metro:bundling:progress","_t":1784047083113,"id":"1","progress":0.718136789793432,"total":603,"current":511} +{"_e":"metro:bundling:progress","_t":1784047083213,"id":"1","progress":0.7760770975056689,"total":630,"current":555} +{"_e":"metro:bundling:progress","_t":1784047083313,"id":"1","progress":0.8181896106249739,"total":639,"current":578} +{"_e":"metro:bundling:progress","_t":1784047083414,"id":"1","progress":0.8370303304662743,"total":658,"current":602} +{"_e":"metro:bundling:progress","_t":1784047083514,"id":"1","progress":0.8849931412894376,"total":675,"current":635} +{"_e":"metro:bundling:progress","_t":1784047083615,"id":"1","progress":0.9597742674118062,"total":689,"current":675} +{"_e":"metro:bundling:progress","_t":1784047083717,"id":"1","progress":0.9942113001470281,"total":690,"current":688} +{"_e":"metro:bundling:progress","_t":1784047083958,"id":"1","progress":0.997103549674438,"total":690,"current":689} +{"_e":"metro:bundling:progress","_t":1784047084066,"id":"1","progress":0.999,"total":693,"current":693} +{"_e":"metro:bundling:progress","_t":1784047084168,"id":"1","progress":0.999,"total":704,"current":704} +{"_e":"metro:bundling:done","_t":1784047084232,"id":"1","total":704,"ms":3814.671042} +{"_e":"metro:client_log","_t":1784047085319,"level":"warn","data":["SafeAreaView has been deprecated and will be removed in a future release. Please use 'react-native-safe-area-context' instead. See https://github.com/AppAndFlow/react-native-safe-area-context"]} diff --git a/examples/react-native-rpc/.expo/devices.json b/examples/react-native-rpc/.expo/devices.json new file mode 100644 index 0000000..5efff6c --- /dev/null +++ b/examples/react-native-rpc/.expo/devices.json @@ -0,0 +1,3 @@ +{ + "devices": [] +} diff --git a/examples/react-native-rpc/.gitignore b/examples/react-native-rpc/.gitignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/examples/react-native-rpc/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/examples/react-native-rpc/App.tsx b/examples/react-native-rpc/App.tsx new file mode 100644 index 0000000..4eb1f05 --- /dev/null +++ b/examples/react-native-rpc/App.tsx @@ -0,0 +1,329 @@ +import { useCallback, useMemo, useState } from "react"; +import { + ActivityIndicator, + FlatList, + KeyboardAvoidingView, + Platform, + Pressable, + SafeAreaView, + StyleSheet, + Text, + TextInput, + View, +} from "react-native"; + +import { CometClient, CometRpcError, type Task } from "./src/comet-rpc"; + +const DEFAULT_API_URL = "http://127.0.0.1:8787"; + +function describeError(error: unknown) { + if (error instanceof CometRpcError) { + return `RPC ${error.status}: ${JSON.stringify(error.body)}`; + } + return error instanceof Error ? error.message : String(error); +} + +function EmptyTasks() { + return No tasks loaded.; +} + +export default function App() { + const [baseUrl, setBaseUrl] = useState(DEFAULT_API_URL); + const [token, setToken] = useState(""); + const [title, setTitle] = useState(""); + const [tasks, setTasks] = useState([]); + const [loading, setLoading] = useState(false); + const [message, setMessage] = useState( + "Set the API URL and bearer token, then load tasks.", + ); + + const client = useMemo( + () => + new CometClient(baseUrl, () => { + const trimmed = token.trim(); + return trimmed.length > 0 ? trimmed : undefined; + }), + [baseUrl, token], + ); + + const run = useCallback(async (action: () => Promise) => { + setLoading(true); + try { + await action(); + } catch (error) { + setMessage(describeError(error)); + } finally { + setLoading(false); + } + }, []); + + const refresh = useCallback( + () => + run(async () => { + const nextTasks = await client.listTasks(); + setTasks(nextTasks); + const suffix = nextTasks.length === 1 ? "" : "s"; + setMessage(`Loaded ${nextTasks.length} task${suffix}.`); + }), + [client, run], + ); + + const createTask = useCallback( + () => + run(async () => { + const trimmed = title.trim(); + if (!trimmed) { + setMessage("Type a title before creating a task."); + return; + } + const task = await client.createTask({ title: trimmed }); + setTitle(""); + setTasks((current) => [task, ...current]); + setMessage(`Created task #${task.id}.`); + }), + [client, run, title], + ); + + const updateCompletedTask = useCallback((completed: Task) => { + setTasks((current) => + current.map((item) => (item.id === completed.id ? completed : item)), + ); + }, []); + + const completeTask = useCallback( + (task: Task) => + run(async () => { + const completed = await client.completeTask(task.id); + updateCompletedTask(completed); + setMessage(`Completed task #${completed.id}.`); + }), + [client, run, updateCompletedTask], + ); + + const keyExtractor = useCallback((item: Task) => String(item.id), []); + + const renderTask = useCallback( + ({ item }: { item: Task }) => ( + + + {item.title} + + #{item.id} - {item.created_at} + + + completeTask(item)} + style={[styles.doneButton, item.done && styles.doneButtonDisabled]} + > + + {item.done ? "Done" : "Complete"} + + + + ), + [completeTask, loading], + ); + + return ( + + + + Comet RPC + React Native client + + + + API base URL + + + Bearer token + + + + + + Create + + + + + Load tasks with listTasks() + + + + + {loading ? : null} + {message} + + + + + + ); +} + +const styles = StyleSheet.create({ + safeArea: { + flex: 1, + backgroundColor: "#f6f7f9", + }, + screen: { + flex: 1, + padding: 20, + gap: 16, + }, + header: { + gap: 4, + }, + kicker: { + color: "#52606d", + fontSize: 13, + fontWeight: "700", + textTransform: "uppercase", + }, + title: { + color: "#101828", + fontSize: 28, + fontWeight: "800", + }, + panel: { + backgroundColor: "#ffffff", + borderColor: "#e1e5ea", + borderRadius: 8, + borderWidth: 1, + gap: 10, + padding: 14, + }, + label: { + color: "#344054", + fontSize: 13, + fontWeight: "700", + }, + input: { + backgroundColor: "#ffffff", + borderColor: "#cfd6df", + borderRadius: 8, + borderWidth: 1, + color: "#101828", + minHeight: 44, + paddingHorizontal: 12, + }, + row: { + alignItems: "center", + flexDirection: "row", + gap: 10, + }, + taskInput: { + flex: 1, + }, + primaryButton: { + alignItems: "center", + backgroundColor: "#176b5d", + borderRadius: 8, + minHeight: 44, + justifyContent: "center", + paddingHorizontal: 16, + }, + primaryButtonText: { + color: "#ffffff", + fontWeight: "800", + }, + secondaryButton: { + alignItems: "center", + borderColor: "#176b5d", + borderRadius: 8, + borderWidth: 1, + minHeight: 44, + justifyContent: "center", + }, + secondaryButtonText: { + color: "#176b5d", + fontWeight: "800", + }, + statusRow: { + alignItems: "center", + flexDirection: "row", + gap: 10, + minHeight: 24, + }, + statusText: { + color: "#475467", + flex: 1, + }, + listContent: { + gap: 10, + paddingBottom: 32, + }, + empty: { + color: "#667085", + paddingVertical: 20, + textAlign: "center", + }, + taskCard: { + alignItems: "center", + backgroundColor: "#ffffff", + borderColor: "#e1e5ea", + borderRadius: 8, + borderWidth: 1, + flexDirection: "row", + gap: 12, + padding: 14, + }, + taskText: { + flex: 1, + gap: 4, + }, + taskTitle: { + color: "#101828", + fontSize: 16, + fontWeight: "700", + }, + taskMeta: { + color: "#667085", + fontSize: 12, + }, + doneButton: { + backgroundColor: "#2f6fed", + borderRadius: 8, + paddingHorizontal: 12, + paddingVertical: 10, + }, + doneButtonDisabled: { + backgroundColor: "#98a2b3", + }, + doneButtonText: { + color: "#ffffff", + fontWeight: "800", + }, +}); diff --git a/examples/react-native-rpc/README.md b/examples/react-native-rpc/README.md new file mode 100644 index 0000000..3e83ec1 --- /dev/null +++ b/examples/react-native-rpc/README.md @@ -0,0 +1,48 @@ +# Comet React Native RPC Example + +This React Native example consumes the typed TypeScript client generated by: + +```sh +cargo run -p comet-cli -- rpc generate \ + --lang ts \ + --path examples/cloudflare-worker \ + --out examples/react-native-rpc/src/comet-rpc.ts +``` + +It talks to the task routes in `examples/cloudflare-worker`: + +- `listTasks()` +- `createTask({ title })` +- `completeTask(id)` + +## Run + +Start the Worker API first: + +```sh +cd examples/cloudflare-worker +npm install +npm run dev +``` + +Then install dependencies and type-check the React Native app: + +```sh +cd examples/react-native-rpc +npm install +npm run typecheck +``` + +To run it on a device or simulator, add the usual React Native native project +folders for your target platform or copy these files into an existing React +Native app, then run `npm run start` and the platform command. In the app, set +the API base URL and a bearer token. The generated RPC client adds +`Authorization: Bearer ` for routes marked as authenticated in the +manifest. + +Use plain `http://` only for local development. For shared networks, devices, +staging, and production, use HTTPS before entering a bearer token. + +For local devices, use a URL reachable from the device or simulator. For +example, Android emulators commonly need `http://10.0.2.2:` instead of +`localhost`. diff --git a/examples/react-native-rpc/app.json b/examples/react-native-rpc/app.json new file mode 100644 index 0000000..3e32215 --- /dev/null +++ b/examples/react-native-rpc/app.json @@ -0,0 +1,4 @@ +{ + "name": "CometRpc", + "displayName": "Comet RPC" +} diff --git a/examples/react-native-rpc/index.ts b/examples/react-native-rpc/index.ts new file mode 100644 index 0000000..bdfbdfb --- /dev/null +++ b/examples/react-native-rpc/index.ts @@ -0,0 +1,6 @@ +import { AppRegistry } from "react-native"; + +import App from "./App"; +import { name as appName } from "./app.json"; + +AppRegistry.registerComponent(appName, () => App); diff --git a/examples/react-native-rpc/package-lock.json b/examples/react-native-rpc/package-lock.json new file mode 100644 index 0000000..3cad102 --- /dev/null +++ b/examples/react-native-rpc/package-lock.json @@ -0,0 +1,5613 @@ +{ + "name": "comet-react-native-rpc-example", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "comet-react-native-rpc-example", + "version": "0.0.0", + "dependencies": { + "react": "19.2.7", + "react-native": "0.86.0" + }, + "devDependencies": { + "@react-native-community/cli": "20.2.0", + "@types/react": "~19.2.17", + "typescript": "~7.0.2" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "devOptional": true, + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "devOptional": true, + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@isaacs/ttlcache": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", + "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodable/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-9uGyhaQavEUMC8AIddIjau4NsnsXhou+j5sBAGojCM1oxmQpVKTWR/9JxABD6UAv12vpIms55fPZKFQEhG6uBg==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/nodable" + } + ], + "license": "MIT" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@react-native-community/cli": { + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-20.2.0.tgz", + "integrity": "sha512-5RZKkBeUFC4hhrzySzxWmRJwXBTc2PE3L6QUgvnJk3QDxNYMY7aGuI8Gr9eZRS1DbiY10IPWqpJfqSIplzVG6A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@react-native-community/cli-clean": "20.2.0", + "@react-native-community/cli-config": "20.2.0", + "@react-native-community/cli-doctor": "20.2.0", + "@react-native-community/cli-server-api": "20.2.0", + "@react-native-community/cli-tools": "20.2.0", + "@react-native-community/cli-types": "20.2.0", + "commander": "^9.4.1", + "deepmerge": "^4.3.0", + "execa": "^5.0.0", + "find-up": "^5.0.0", + "fs-extra": "^8.1.0", + "graceful-fs": "^4.1.3", + "picocolors": "^1.1.1", + "prompts": "^2.4.2", + "semver": "^7.5.2" + }, + "bin": { + "rnc-cli": "build/bin.js" + }, + "engines": { + "node": ">=20.19.4" + } + }, + "node_modules/@react-native-community/cli-clean": { + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-20.2.0.tgz", + "integrity": "sha512-krqbhFiwHN8l5wZ2XTcrNq9N+DqDWHxW8RK0bIcrK0O+fMvjYi9e/1Pnx2W0CTotOVcZpHzUbgZ4TQVF231Skw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@react-native-community/cli-tools": "20.2.0", + "execa": "^5.0.0", + "fast-glob": "^3.3.2", + "picocolors": "^1.1.1" + } + }, + "node_modules/@react-native-community/cli-config": { + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-20.2.0.tgz", + "integrity": "sha512-GF5FxgDfOSLPi/bSiE/xvOGELwzV2GLQnDED+7ArbPi01W1Vu+hD/m+J+bwX34Pocpo767eXAVxc2h9WFUTUfQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@react-native-community/cli-tools": "20.2.0", + "cosmiconfig": "^9.0.0", + "deepmerge": "^4.3.0", + "fast-glob": "^3.3.2", + "joi": "^17.2.1", + "picocolors": "^1.1.1" + } + }, + "node_modules/@react-native-community/cli-config-android": { + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-config-android/-/cli-config-android-20.2.0.tgz", + "integrity": "sha512-lASofUNBVK0Pq3VEJ1JrCfAW94Rnk38DikauycRcoHl0hWjHIN3XhsN20dEq+CaIJoq37aCg5NSSSpmkET+ShA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@react-native-community/cli-tools": "20.2.0", + "fast-glob": "^3.3.2", + "fast-xml-parser": "^5.3.6", + "picocolors": "^1.1.1" + } + }, + "node_modules/@react-native-community/cli-config-apple": { + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-config-apple/-/cli-config-apple-20.2.0.tgz", + "integrity": "sha512-oohr6BV2riWJ5PYjayi1u52bG3H95MwKPkJCZo9pnOlYTjifZQVkMxZ8VTuk392efh0bijVTYfxH8iJPi97SIQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@react-native-community/cli-tools": "20.2.0", + "execa": "^5.0.0", + "fast-glob": "^3.3.2", + "picocolors": "^1.1.1" + } + }, + "node_modules/@react-native-community/cli-doctor": { + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-20.2.0.tgz", + "integrity": "sha512-eZjlwmjPoBXgyD6nV5oDDocL1VH5UW4LxZcMAqyN3rQw5vq3CeJikFpNedKTtSl3P6JizIkInyIHpfrW+9qfJA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@react-native-community/cli-config": "20.2.0", + "@react-native-community/cli-platform-android": "20.2.0", + "@react-native-community/cli-platform-apple": "20.2.0", + "@react-native-community/cli-platform-ios": "20.2.0", + "@react-native-community/cli-tools": "20.2.0", + "command-exists": "^1.2.8", + "deepmerge": "^4.3.0", + "envinfo": "^7.13.0", + "execa": "^5.0.0", + "node-stream-zip": "^1.9.1", + "ora": "^5.4.1", + "picocolors": "^1.1.1", + "semver": "^7.5.2", + "wcwidth": "^1.0.1", + "yaml": "^2.2.1" + } + }, + "node_modules/@react-native-community/cli-platform-android": { + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-20.2.0.tgz", + "integrity": "sha512-fRglzcf/Yq5lnIkqdA8uf1GvlG18x3Dm48Yvo+l7KiWgh/SVtfVhfvalrtX5rmx+KeJkDA534bGoPvi5+ruWjw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@react-native-community/cli-config-android": "20.2.0", + "@react-native-community/cli-tools": "20.2.0", + "execa": "^5.0.0", + "logkitty": "^0.7.1", + "picocolors": "^1.1.1" + } + }, + "node_modules/@react-native-community/cli-platform-apple": { + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-apple/-/cli-platform-apple-20.2.0.tgz", + "integrity": "sha512-jkEPLAd8C/ZRu39a3nhxwSXe0iisUiJC808GExnrnTcViLtg3sad2ciQ/HjjvbdsPCS9sT+Jr7xh3MzBsPojsQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@react-native-community/cli-config-apple": "20.2.0", + "@react-native-community/cli-tools": "20.2.0", + "execa": "^5.0.0", + "fast-xml-parser": "^5.3.6", + "picocolors": "^1.1.1" + } + }, + "node_modules/@react-native-community/cli-platform-ios": { + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-20.2.0.tgz", + "integrity": "sha512-bCKlBt2HoD7WTl/HX60+4HFhR1lKY64Y9MPWu7yWQwOCEqYRMi6MkRxLimiONj9M2/lNf0z9BPHDzdDe5HM2ag==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@react-native-community/cli-platform-apple": "20.2.0" + } + }, + "node_modules/@react-native-community/cli-server-api": { + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-20.2.0.tgz", + "integrity": "sha512-AI1fsl+6LNuOoDcdWsnF3s9ozqminGCUlbFcrgdZIJWyOuFBeQclNydI22VO3vWaO4dHQkfVa3Zo10AncWQvwA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@react-native-community/cli-tools": "20.2.0", + "body-parser": "^2.2.2", + "compression": "^1.7.1", + "connect": "^3.6.5", + "errorhandler": "^1.5.1", + "nocache": "^3.0.1", + "open": "^6.2.0", + "pretty-format": "^29.7.0", + "serve-static": "^1.13.1", + "ws": "^6.2.3" + } + }, + "node_modules/@react-native-community/cli-tools": { + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-20.2.0.tgz", + "integrity": "sha512-A5W2nqlTidPzGyXzS57jvHsMpQd8iOGSjePj4H318uMbhIdeIHQ5e59fNbdHahS62ISs+2p9s78sVHMbGVa1bg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@vscode/sudo-prompt": "^9.0.0", + "appdirsjs": "^1.2.4", + "execa": "^5.0.0", + "find-up": "^5.0.0", + "launch-editor": "^2.9.1", + "mime": "^2.4.1", + "ora": "^5.4.1", + "picocolors": "^1.1.1", + "prompts": "^2.4.2", + "semver": "^7.5.2" + } + }, + "node_modules/@react-native-community/cli-types": { + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-20.2.0.tgz", + "integrity": "sha512-K60zY/ly8G9rGJQzZ+bFRyLEckAUzyF8Fu9a2Kw6JipCrOl7SiVRBVqjvOr/XnCRKuOpH8i9y7RVg07wkSGRBQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "joi": "^17.2.1" + } + }, + "node_modules/@react-native/assets-registry": { + "version": "0.86.0", + "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.86.0.tgz", + "integrity": "sha512-nIaXbm2jX1OTYp0qbviJ3O6KZivoE8z3BnhUQ2LsqfZSWRoOK/n1qsiAr6oALiNKWnXY3j2KPwtYORnZzp8xew==", + "license": "MIT", + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/codegen": { + "version": "0.86.0", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.86.0.tgz", + "integrity": "sha512-uTs9DBo3+/lUqinsGZK0FKJRBVClrwMXoZToaDxE1Q2SL2e55vs2GwyZfIKzPl5uJnbu4PfFMIp0/mLXLWUMuA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/parser": "^7.29.0", + "hermes-parser": "0.36.0", + "invariant": "^2.2.4", + "nullthrows": "^1.1.1", + "tinyglobby": "^0.2.15", + "yargs": "^17.6.2" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/codegen/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@react-native/codegen/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native/codegen/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@react-native/codegen/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/@react-native/codegen/node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@react-native/codegen/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/@react-native/community-cli-plugin": { + "version": "0.86.0", + "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.86.0.tgz", + "integrity": "sha512-Jv8p1ebEPfTzs8gmrjsdT2XMXFfeAg45Pman+XPLFGaSeGAZkutRFRyX9Cs9aGTSOyIA9YPJ6vDNb1ayTf1FKQ==", + "license": "MIT", + "dependencies": { + "@react-native/dev-middleware": "0.86.0", + "debug": "^4.4.0", + "invariant": "^2.2.4", + "metro": "^0.84.3", + "metro-config": "^0.84.3", + "metro-core": "^0.84.3", + "semver": "^7.1.3" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + }, + "peerDependencies": { + "@react-native-community/cli": "*", + "@react-native/metro-config": "0.86.0" + }, + "peerDependenciesMeta": { + "@react-native-community/cli": { + "optional": true + }, + "@react-native/metro-config": { + "optional": true + } + } + }, + "node_modules/@react-native/debugger-frontend": { + "version": "0.86.0", + "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.86.0.tgz", + "integrity": "sha512-7Mb3nDfyJeys+ELF75Ageu7VKERlnIMoO+aNPoXqTXvz+b41L6l2CqMyLpDHxkBSlenij6gEepPNgaIyWHbJZw==", + "license": "BSD-3-Clause", + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/debugger-shell": { + "version": "0.86.0", + "resolved": "https://registry.npmjs.org/@react-native/debugger-shell/-/debugger-shell-0.86.0.tgz", + "integrity": "sha512-Y0zEkZzLz8ou6o/VLml1A31X/rMgc6DRjwxwzPMa94qRTMY070WeBCNTITQo4kKTBAUgbxh07oXPQqp0Tpja8w==", + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.6", + "debug": "^4.4.0", + "fb-dotslash": "0.5.8" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/dev-middleware": { + "version": "0.86.0", + "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.86.0.tgz", + "integrity": "sha512-20pTO6yTybmvXvro520H6C7jydIQnLKOl5qFtVEcHSdFrY63r3OGei+Rx9bILgSRmH6jgnfEcijcMx7pwWuQtw==", + "license": "MIT", + "dependencies": { + "@isaacs/ttlcache": "^1.4.1", + "@react-native/debugger-frontend": "0.86.0", + "@react-native/debugger-shell": "0.86.0", + "chrome-launcher": "^0.15.2", + "chromium-edge-launcher": "^0.3.0", + "connect": "^3.6.5", + "debug": "^4.4.0", + "invariant": "^2.2.4", + "nullthrows": "^1.1.1", + "open": "^7.0.3", + "serve-static": "^1.16.2", + "ws": "^7.5.10" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/dev-middleware/node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native/dev-middleware/node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native/dev-middleware/node_modules/ws": { + "version": "7.5.11", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.11.tgz", + "integrity": "sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA==", + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@react-native/gradle-plugin": { + "version": "0.86.0", + "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.86.0.tgz", + "integrity": "sha512-a1RcfaEDqWExCGfCwadIxt4l8FvKYgFqeMf2uzeKyAOnb+vTGNIeCvifFL2MqvgaeYxlER437HbMIajGcuJ1pQ==", + "license": "MIT", + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/js-polyfills": { + "version": "0.86.0", + "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.86.0.tgz", + "integrity": "sha512-zYy/Cjd1VTnZ2iCNaG9bDF9C3l2ntESiPRscjIlI5FKugu6aeTwsDSv1aI8Bc4Kp3vEdoVg+UQhLAhE4svREaQ==", + "license": "MIT", + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/normalize-colors": { + "version": "0.86.0", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.86.0.tgz", + "integrity": "sha512-kG0wfCGghUKlfxkJyyHCDVutWVYWK7/DG58ojA/4v9EfulgF+osuSQmlbNb3rcKX58qutm7JcldSeVLgGFha9g==", + "license": "MIT" + }, + "node_modules/@react-native/virtualized-lists": { + "version": "0.86.0", + "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.86.0.tgz", + "integrity": "sha512-4/ZLXdf/OSpPDVO0AsQ1SJdRIzt5t9BNQ46QwGgxvX7/cirYR5k8KXctNGGgW8lQo2gZChEfY2zFCZg9nM/jiw==", + "license": "MIT", + "dependencies": { + "invariant": "^2.2.4", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + }, + "peerDependencies": { + "@types/react": "^19.2.0", + "react": "*", + "react-native": "0.86.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "devOptional": true, + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "devOptional": true, + "license": "BSD-3-Clause" + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "devOptional": true, + "license": "BSD-3-Clause" + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.10", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/node": { + "version": "26.1.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.1.tgz", + "integrity": "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==", + "license": "MIT", + "dependencies": { + "undici-types": "~8.3.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "license": "MIT" + }, + "node_modules/@typescript/typescript-aix-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", + "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", + "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz", + "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz", + "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz", + "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz", + "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz", + "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-loong64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz", + "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==", + "cpu": [ + "loong64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-mips64el": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz", + "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==", + "cpu": [ + "mips64el" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz", + "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-riscv64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz", + "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-s390x": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz", + "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz", + "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz", + "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz", + "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz", + "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz", + "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-sunos-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz", + "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz", + "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", + "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@vscode/sudo-prompt": { + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/@vscode/sudo-prompt/-/sudo-prompt-9.3.2.tgz", + "integrity": "sha512-gcXoCN00METUNFeQOFJ+C9xUI0DKB+0EGMVg7wbVYRHBw2Eq3fKisDZOkRdOz3kqXRKOENMfShPOmypw1/8nOw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/anser": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz", + "integrity": "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==", + "license": "MIT" + }, + "node_modules/ansi-fragments": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ansi-fragments/-/ansi-fragments-0.2.1.tgz", + "integrity": "sha512-DykbNHxuXQwUDRv5ibc2b0x7uw7wmwOGLBUd5RmaQ5z8Lhx19vwvKV+FAsM5rEA6dEcHxX+/Ad5s9eF2k2bB+w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "colorette": "^1.0.7", + "slice-ansi": "^2.0.0", + "strip-ansi": "^5.0.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anynum": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/anynum/-/anynum-1.0.1.tgz", + "integrity": "sha512-N6//FLET/tXYNM/F6ABca1oH6fWB+KlTt909Le28WMDBk8oaT4vY17DCrwg2MvmuqUKt3Ni4N5dGJ/EoBgcO6A==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/appdirsjs": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/appdirsjs/-/appdirsjs-1.2.7.tgz", + "integrity": "sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "devOptional": true, + "license": "Python-2.0" + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "license": "MIT" + }, + "node_modules/astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/babel-plugin-syntax-hermes-parser": { + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.36.0.tgz", + "integrity": "sha512-LhD0xdoedDw7ansQgXbB2DADLZIK/LRXuWNBPuVzMc5S2WK5GyT89tCM+cQzxFGO0mGyLK6D5TrVOJJzAoDy8Q==", + "license": "MIT", + "dependencies": { + "hermes-parser": "0.36.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.43", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz", + "integrity": "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/body-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", + "on-finished": "^2.4.1", + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.6.tgz", + "integrity": "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.42", + "caniuse-lite": "^1.0.30001803", + "electron-to-chromium": "^1.5.389", + "node-releases": "^2.0.51", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001805", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001805.tgz", + "integrity": "sha512-52noaS3DubycKSXaU30TwPGIp+POyQSUVa5jBEq3vkRkY0kjyb3LQgvhU6WGyCcyXqVLWO0Cw0Q6BSdD0kUfVA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chrome-launcher": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", + "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==", + "license": "Apache-2.0", + "dependencies": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0" + }, + "bin": { + "print-chrome-path": "bin/print-chrome-path.js" + }, + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/chrome-launcher/node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chromium-edge-launcher": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-0.3.0.tgz", + "integrity": "sha512-p03azHlGjtyRvFEee3cyvtsRYdniSkwjkzmM/KmVnqT5d7QkkwpJBhis/zCLMYdQMVJ5tt140TBNqqrZPaWeFA==", + "license": "Apache-2.0", + "dependencies": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0", + "mkdirp": "^1.0.4" + } + }, + "node_modules/chromium-edge-launcher/node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "license": "MIT" + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/command-exists": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", + "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/connect/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/connect/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.2.tgz", + "integrity": "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz", + "integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.389", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.389.tgz", + "integrity": "sha512-cEto7aeOqBfU1D+c5py5pE+ooscKE75JifxLBdFUZsqAxRS6y7kebtxAZvICszSl05gPjYHDTjY+lXpyGvpJbg==", + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/envinfo": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.21.0.tgz", + "integrity": "sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==", + "devOptional": true, + "license": "MIT", + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/error-stack-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "license": "MIT", + "dependencies": { + "stackframe": "^1.3.4" + } + }, + "node_modules/errorhandler": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.2.tgz", + "integrity": "sha512-kNAL7hESndBCrWwS72QyV3IVOTrVmj9D062FV5BQswNL5zEdeRmz/WJFyh6Aj/plvvSOrzddkxW57HgkZcR9Fw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "escape-html": "~1.0.3" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "license": "Apache-2.0" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-xml-builder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.3.0.tgz", + "integrity": "sha512-F74cZEdCvuw9P41GAC3rod4X04jjWGM1JPEv/GWSqFTWLsdyMSBMBMlm9Hk3GLBgLBbdBNY8yee0pQh2RBVESQ==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "path-expression-matcher": "^1.6.2", + "xml-naming": "^0.3.0" + } + }, + "node_modules/fast-xml-parser": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.10.0.tgz", + "integrity": "sha512-SLhnTEqE5QpJHq/6zl9bsmImEP2adv+y6Wy+cJa7nVTRzQh1OZfCe9k29M5xN74LWnu0xa1zrUrq3KnOKl92Fg==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "@nodable/entities": "^2.2.0", + "fast-xml-builder": "^1.2.0", + "is-unsafe": "^2.0.0", + "path-expression-matcher": "^1.6.2", + "strnum": "^2.4.1", + "xml-naming": "^0.3.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-dotslash": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/fb-dotslash/-/fb-dotslash-0.5.8.tgz", + "integrity": "sha512-XHYLKk9J4BupDxi9bSEhkfss0m+Vr9ChTrjhf9l2iw3jB5C7BnY4GVPoMcqbrTutsKJso6yj2nAB6BI/F2oZaA==", + "license": "(MIT OR Apache-2.0)", + "bin": { + "dotslash": "bin/dotslash" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/finalhandler/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flow-enums-runtime": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz", + "integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==", + "license": "MIT" + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "devOptional": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hermes-compiler": { + "version": "250829098.0.14", + "resolved": "https://registry.npmjs.org/hermes-compiler/-/hermes-compiler-250829098.0.14.tgz", + "integrity": "sha512-5meXwsZxgiqFaJjNzwjzI9IyUkuGGBisu+z9BvQWmGVpjH6nz11hgqkyxe4dl8UAdyIV4lTbz91+Dlnjz0VxqA==", + "license": "MIT" + }, + "node_modules/hermes-estree": { + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.36.0.tgz", + "integrity": "sha512-A1+8zn5oss2CFP7pKsOaxorQG6FNIz1WU1VDqruLPPZl3LVgeE2C5xfFg8Ow6/Ow4mSslLLtYP1J3n38eKyW9w==", + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.36.0.tgz", + "integrity": "sha512-GdpwMmH5x6IpC1cijvcvYnlPB60Mh6kTSF/NFdYV/j56gYdi+0RIakYs+eqOV+bbO0SW7mgVVGSsTJxyPQfo3w==", + "license": "MIT", + "dependencies": { + "hermes-estree": "0.36.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/image-size": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz", + "integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==", + "license": "MIT", + "dependencies": { + "queue": "6.0.2" + }, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=16.x" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unsafe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-unsafe/-/is-unsafe-2.0.0.tgz", + "integrity": "sha512-2LdV822R+wmI86unXA93WCFpL6g+av8ynWk0nrHyJqGop5VoocYsSLFgN8jrfalT6iGeLNM4KXuVSsULP53kEA==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util/node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/joi": { + "version": "17.13.4", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.4.tgz", + "integrity": "sha512-1RuuER6kmt8K8I3nIWvPZKi5RQCb568ZPyY4Pwjlua+yo+63ZTmIwxLZH0heBmiKN4uxjvCiarDrjaeH84xicQ==", + "devOptional": true, + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsc-safe-url": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/jsc-safe-url/-/jsc-safe-url-0.2.4.tgz", + "integrity": "sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==", + "license": "0BSD" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "devOptional": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/launch-editor": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.14.1.tgz", + "integrity": "sha512-QWBrQsMpH7gPr965dsKD/3cKWiNoTjpATQf++Xq63N6sKRGMwlVXz41O1IZTMfZQgBctD/K5Zt06+/I6pP6+HA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.1.1", + "shell-quote": "^1.8.4" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lighthouse-logger": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz", + "integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==", + "license": "Apache-2.0", + "dependencies": { + "debug": "^2.6.9", + "marky": "^1.2.2" + } + }, + "node_modules/lighthouse-logger/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/lighthouse-logger/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/logkitty": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/logkitty/-/logkitty-0.7.1.tgz", + "integrity": "sha512-/3ER20CTTbahrCrpYfPn7Xavv9diBROZpoXGVZDWMw4b/X4uuUwAC0ki85tgsdMRONURyIJbcOvS94QsUBYPbQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "ansi-fragments": "^0.2.1", + "dayjs": "^1.8.15", + "yargs": "^15.1.0" + }, + "bin": { + "logkitty": "bin/logkitty.js" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/marky": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/marky/-/marky-1.3.0.tgz", + "integrity": "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==", + "license": "Apache-2.0" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", + "license": "MIT" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/metro": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro/-/metro-0.84.4.tgz", + "integrity": "sha512-8ETTubqfD6ornDy2zYDvRcKnVDOXdFJsjetYDBsY4oAsb6NJkiwFR+FaMESyGppFmQUyBQA4H4sFGxzcQSGtFA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/core": "^7.25.2", + "@babel/generator": "^7.29.1", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "accepts": "^2.0.0", + "ci-info": "^2.0.0", + "connect": "^3.6.5", + "debug": "^4.4.0", + "error-stack-parser": "^2.0.6", + "flow-enums-runtime": "^0.0.6", + "graceful-fs": "^4.2.4", + "hermes-parser": "0.35.0", + "image-size": "^1.0.2", + "invariant": "^2.2.4", + "jest-worker": "^29.7.0", + "jsc-safe-url": "^0.2.2", + "lodash.throttle": "^4.1.1", + "metro-babel-transformer": "0.84.4", + "metro-cache": "0.84.4", + "metro-cache-key": "0.84.4", + "metro-config": "0.84.4", + "metro-core": "0.84.4", + "metro-file-map": "0.84.4", + "metro-resolver": "0.84.4", + "metro-runtime": "0.84.4", + "metro-source-map": "0.84.4", + "metro-symbolicate": "0.84.4", + "metro-transform-plugins": "0.84.4", + "metro-transform-worker": "0.84.4", + "mime-types": "^3.0.1", + "nullthrows": "^1.1.1", + "serialize-error": "^2.1.0", + "source-map": "^0.5.6", + "throat": "^5.0.0", + "ws": "^7.5.10", + "yargs": "^17.6.2" + }, + "bin": { + "metro": "src/cli.js" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/metro-babel-transformer": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.84.4.tgz", + "integrity": "sha512-rvCfz8snl9h20VcvpOHxZuHP1SlAkv4HXbzw7nyyVwu6Eqo5PRerbakQ9XmUCOsRy70spJ37O+G1TK8oMzo48g==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.2", + "flow-enums-runtime": "^0.0.6", + "hermes-parser": "0.35.0", + "metro-cache-key": "0.84.4", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/metro-babel-transformer/node_modules/hermes-estree": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.35.0.tgz", + "integrity": "sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg==", + "license": "MIT" + }, + "node_modules/metro-babel-transformer/node_modules/hermes-parser": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.35.0.tgz", + "integrity": "sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA==", + "license": "MIT", + "dependencies": { + "hermes-estree": "0.35.0" + } + }, + "node_modules/metro-cache": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.84.4.tgz", + "integrity": "sha512-gpcFQdSLUwUCk71saKoE64jLFbx2nwTfVCcPSULMNT8QYq0p1eZZE29Jvd0HtT/UlhC3ZOutLxJME5xqD2JUZg==", + "license": "MIT", + "dependencies": { + "exponential-backoff": "^3.1.1", + "flow-enums-runtime": "^0.0.6", + "https-proxy-agent": "^7.0.5", + "metro-core": "0.84.4" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/metro-cache-key": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.84.4.tgz", + "integrity": "sha512-wVO79aGrkYImpnaVS4+d5RrRBRPX31QtvKB3wKGBuiNSznduZTQHzsrJZRroFJSwnygrzdsGUtDQPuqqFjFdvw==", + "license": "MIT", + "dependencies": { + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/metro-config": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.84.4.tgz", + "integrity": "sha512-PMotGDjXcXLWo2TMRH+VR99phFNgYTwqh4OoieIKK3yTJa1Jmkl+fZJxDO0jfBvNF+WESHciHvpNuBtXaF3B0Q==", + "license": "MIT", + "dependencies": { + "connect": "^3.6.5", + "flow-enums-runtime": "^0.0.6", + "jest-validate": "^29.7.0", + "metro": "0.84.4", + "metro-cache": "0.84.4", + "metro-core": "0.84.4", + "metro-runtime": "0.84.4", + "yaml": "^2.6.1" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/metro-core": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.84.4.tgz", + "integrity": "sha512-HONpWC5LGXZn3ffkd4Hu6AIrfE7j4Z0g0wMo/goV24WOB3lhuFZ40KgvaDiSw8iyQHloMYay5N/wPX+z8oN/PQ==", + "license": "MIT", + "dependencies": { + "flow-enums-runtime": "^0.0.6", + "lodash.throttle": "^4.1.1", + "metro-resolver": "0.84.4" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/metro-file-map": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.84.4.tgz", + "integrity": "sha512-KSVDi/u60hKPx++NLu3MTIvyjzNoJnFAF8PQFxaj1jiSka/wjw+Ua6sNuJ0TDHQv+7AAoFQxeMgaRAe8Yic5wQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "fb-watchman": "^2.0.0", + "flow-enums-runtime": "^0.0.6", + "graceful-fs": "^4.2.4", + "invariant": "^2.2.4", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "nullthrows": "^1.1.1", + "walker": "^1.0.7" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/metro-minify-terser": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.84.4.tgz", + "integrity": "sha512-5qpbaVOMC7CPitIpuewzVeGw7E+C3ykbv2mqTjQLl85Z3annSVGlSCTcsZjqXZzjupfK4Ztj3dDc4kc44NZwtQ==", + "license": "MIT", + "dependencies": { + "flow-enums-runtime": "^0.0.6", + "terser": "^5.15.0" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/metro-resolver": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.84.4.tgz", + "integrity": "sha512-1qLgbxQ5ZGhhutuPot1Yp348ofDsATL2WkrHF65TobqTT9K3P9qJXw38bomk7ncp5B7OYMfWwtyBZo1lCV792A==", + "license": "MIT", + "dependencies": { + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/metro-runtime": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.84.4.tgz", + "integrity": "sha512-Jibypds4g7AhzdRKY+kDoj51s5EXMwgyp5ddtlreDAsWefMdOx+agWqgm0H2XSZ/ueanHHVM89fnf5OJnlxa8Q==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.0", + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/metro-source-map": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.84.4.tgz", + "integrity": "sha512-jbWkPxIesVuo1IWkvezmMJld6iu8nD62GsrZiV6jP37AOdbo4OBq1FJ+qkOg8sV05wAHB//jAbziuW0SlJfW4g==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "flow-enums-runtime": "^0.0.6", + "invariant": "^2.2.4", + "metro-symbolicate": "0.84.4", + "nullthrows": "^1.1.1", + "ob1": "0.84.4", + "source-map": "^0.5.6", + "vlq": "^1.0.0" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/metro-symbolicate": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.84.4.tgz", + "integrity": "sha512-OnfpacxUqGPZQ27t8qK9mFa7uqHIlVWeqRqkCbvMvreEBiamEeOn8krKtcwgP5M4cYDPwuSmCTopHMVthqG4zA==", + "license": "MIT", + "dependencies": { + "flow-enums-runtime": "^0.0.6", + "invariant": "^2.2.4", + "metro-source-map": "0.84.4", + "nullthrows": "^1.1.1", + "source-map": "^0.5.6", + "vlq": "^1.0.0" + }, + "bin": { + "metro-symbolicate": "src/index.js" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/metro-transform-plugins": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.84.4.tgz", + "integrity": "sha512-kehr6HbAecqD0/a3xLXobELdPaAmRAl8bel0qagPF4vhZtux93nS8S4eq2kgKt6J2GnQpVjSoW1PXdst04mwow==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/generator": "^7.29.1", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "flow-enums-runtime": "^0.0.6", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/metro-transform-worker": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.84.4.tgz", + "integrity": "sha512-W1IYMvvXTu4MxYr7d9h7CeG2vpIr3bmLLIavkPY4O1ilzDrvS8z/NEe6y+pC44Ff7raMXQgYSfdqDUwN/i39gg==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/generator": "^7.29.1", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "flow-enums-runtime": "^0.0.6", + "metro": "0.84.4", + "metro-babel-transformer": "0.84.4", + "metro-cache": "0.84.4", + "metro-cache-key": "0.84.4", + "metro-minify-terser": "0.84.4", + "metro-source-map": "0.84.4", + "metro-transform-plugins": "0.84.4", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/metro/node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/metro/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/metro/node_modules/hermes-estree": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.35.0.tgz", + "integrity": "sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg==", + "license": "MIT" + }, + "node_modules/metro/node_modules/hermes-parser": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.35.0.tgz", + "integrity": "sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA==", + "license": "MIT", + "dependencies": { + "hermes-estree": "0.35.0" + } + }, + "node_modules/metro/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/metro/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/metro/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/metro/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/metro/node_modules/ws": { + "version": "7.5.11", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.11.tgz", + "integrity": "sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA==", + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/metro/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/metro/node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/metro/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "devOptional": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nocache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/nocache/-/nocache-3.0.4.tgz", + "integrity": "sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.51", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/node-stream-zip": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz", + "integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/antelle" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nullthrows": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", + "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", + "license": "MIT" + }, + "node_modules/ob1": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.84.4.tgz", + "integrity": "sha512-eJXMpz4aQHXF/YBB9ddqZDIS+ooO91hObo9FoW/xBkr54/zCwYYCDqT/O54vNo8kOkWs5Ou/y28NgdrV0edQNA==", + "license": "MIT", + "dependencies": { + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/open/-/open-6.4.0.tgz", + "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^1.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-expression-matcher": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.6.2.tgz", + "integrity": "sha512-enSlaiat05iasnzmgNxRj8reFdj3puY2QpNgP1aPIaVfT6nn9ICuPoFlKHk8EN22HcwewshO+mN2DGbkCEOtqQ==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/promise": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", + "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", + "license": "MIT", + "dependencies": { + "asap": "~2.0.6" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/qs": { + "version": "6.15.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", + "devOptional": true, + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", + "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", + "license": "MIT", + "dependencies": { + "inherits": "~2.0.3" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-devtools-core": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-6.1.5.tgz", + "integrity": "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==", + "license": "MIT", + "dependencies": { + "shell-quote": "^1.6.1", + "ws": "^7" + } + }, + "node_modules/react-devtools-core/node_modules/ws": { + "version": "7.5.11", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.11.tgz", + "integrity": "sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA==", + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/react-native": { + "version": "0.86.0", + "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.86.0.tgz", + "integrity": "sha512-17ALh/dd6AO4pgOVmOO5Axll5PbErEo3XFyLokyzW6usyi+OShIEPwUW26wLPlhVifgSOIfECCH0WN+0IqtJ1w==", + "license": "MIT", + "dependencies": { + "@react-native/assets-registry": "0.86.0", + "@react-native/codegen": "0.86.0", + "@react-native/community-cli-plugin": "0.86.0", + "@react-native/gradle-plugin": "0.86.0", + "@react-native/js-polyfills": "0.86.0", + "@react-native/normalize-colors": "0.86.0", + "@react-native/virtualized-lists": "0.86.0", + "abort-controller": "^3.0.0", + "anser": "^1.4.9", + "ansi-regex": "^5.0.0", + "babel-plugin-syntax-hermes-parser": "0.36.0", + "base64-js": "^1.5.1", + "commander": "^12.0.0", + "flow-enums-runtime": "^0.0.6", + "hermes-compiler": "250829098.0.14", + "invariant": "^2.2.4", + "memoize-one": "^5.0.0", + "metro-runtime": "^0.84.3", + "metro-source-map": "^0.84.3", + "nullthrows": "^1.1.1", + "pretty-format": "^29.7.0", + "promise": "^8.3.0", + "react-devtools-core": "^6.1.5", + "react-refresh": "^0.14.0", + "regenerator-runtime": "^0.13.2", + "scheduler": "0.27.0", + "semver": "^7.1.3", + "stacktrace-parser": "^0.1.10", + "tinyglobby": "^0.2.15", + "whatwg-fetch": "^3.0.0", + "ws": "^7.5.10", + "yargs": "^17.6.2" + }, + "bin": { + "react-native": "cli.js" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + }, + "peerDependencies": { + "@react-native/jest-preset": "0.86.0", + "@types/react": "^19.1.1", + "react": "^19.2.3" + }, + "peerDependenciesMeta": { + "@react-native/jest-preset": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-native/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/react-native/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/react-native/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/react-native/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/react-native/node_modules/ws": { + "version": "7.5.11", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.11.tgz", + "integrity": "sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA==", + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/react-native/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/react-native/node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/react-native/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "devOptional": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/send/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/send/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serialize-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", + "integrity": "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-static/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.10.0.tgz", + "integrity": "sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/slice-ansi/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/slice-ansi/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackframe": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", + "license": "MIT" + }, + "node_modules/stacktrace-parser": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.11.tgz", + "integrity": "sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==", + "license": "MIT", + "dependencies": { + "type-fest": "^0.7.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strnum": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.4.1.tgz", + "integrity": "sha512-M9eUSMT2dCB2cTNPG7UYj6KuK7RJR2SN2+yCV/fTW3xzTCS6EaGZ5pSMgDIjB7r8zSfTGk+dvvn9rTjpVS9Mwg==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "anynum": "^1.0.1" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/terser": { + "version": "5.49.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.49.0.tgz", + "integrity": "sha512-SNiDnXyHSrxVcIOtVbULzcTmniUiwcV7Nwdyj1twVubeTmbjoa8p69KKDpfkdoOavuM4/GRm1+ykI8qqnavHoA==", + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, + "node_modules/throat": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", + "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "license": "BSD-3-Clause" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-fest": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", + "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=8" + } + }, + "node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/typescript": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", + "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc" + }, + "engines": { + "node": ">=16.20.0" + }, + "optionalDependencies": { + "@typescript/typescript-aix-ppc64": "7.0.2", + "@typescript/typescript-darwin-arm64": "7.0.2", + "@typescript/typescript-darwin-x64": "7.0.2", + "@typescript/typescript-freebsd-arm64": "7.0.2", + "@typescript/typescript-freebsd-x64": "7.0.2", + "@typescript/typescript-linux-arm": "7.0.2", + "@typescript/typescript-linux-arm64": "7.0.2", + "@typescript/typescript-linux-loong64": "7.0.2", + "@typescript/typescript-linux-mips64el": "7.0.2", + "@typescript/typescript-linux-ppc64": "7.0.2", + "@typescript/typescript-linux-riscv64": "7.0.2", + "@typescript/typescript-linux-s390x": "7.0.2", + "@typescript/typescript-linux-x64": "7.0.2", + "@typescript/typescript-netbsd-arm64": "7.0.2", + "@typescript/typescript-netbsd-x64": "7.0.2", + "@typescript/typescript-openbsd-arm64": "7.0.2", + "@typescript/typescript-openbsd-x64": "7.0.2", + "@typescript/typescript-sunos-x64": "7.0.2", + "@typescript/typescript-win32-arm64": "7.0.2", + "@typescript/typescript-win32-x64": "7.0.2" + } + }, + "node_modules/undici-types": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "license": "MIT" + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vlq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz", + "integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==", + "license": "MIT" + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/whatwg-fetch": { + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", + "license": "MIT" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ws": { + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.4.tgz", + "integrity": "sha512-PNIUUyLI5YpkJZj60YBzX1o0ByQ4ovvfmq9N/Kig/PAYbVlGyz4R6G0SEWrD0O9acc0sT2+IdMBVLFv8FSi0Nw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "async-limiter": "~1.0.0" + } + }, + "node_modules/xml-naming": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.3.0.tgz", + "integrity": "sha512-ghig2TBE/H11aOVgmahA3MhimvkBr6JIYknH/Dhdk10nXwdbIqBJsbfMxpvFPG8bAw77gN29aQWvKpmVoPlvPQ==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs-parser/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/examples/react-native-rpc/package.json b/examples/react-native-rpc/package.json new file mode 100644 index 0000000..917ec89 --- /dev/null +++ b/examples/react-native-rpc/package.json @@ -0,0 +1,21 @@ +{ + "name": "comet-react-native-rpc-example", + "version": "0.0.0", + "private": true, + "main": "index.ts", + "scripts": { + "start": "react-native start", + "android": "react-native run-android", + "ios": "react-native run-ios", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "react": "19.2.7", + "react-native": "0.86.0" + }, + "devDependencies": { + "@react-native-community/cli": "20.2.0", + "@types/react": "~19.2.17", + "typescript": "~7.0.2" + } +} diff --git a/examples/react-native-rpc/src/comet-rpc.ts b/examples/react-native-rpc/src/comet-rpc.ts new file mode 100644 index 0000000..7370501 --- /dev/null +++ b/examples/react-native-rpc/src/comet-rpc.ts @@ -0,0 +1,107 @@ +/* Generated by comet rpc generate. Do not edit by hand. */ + +export type TokenProvider = () => string | null | undefined | Promise; + +export interface BoardRow { + id: number; + org_id: number; + title: string; +} + +export interface NewTask { + title: string; +} + +export interface PrivateMeResponse { + session_id: string; + user_id: string; + email?: string | null; +} + +export interface Task { + id: number; + title: string; + done: boolean; + created_at: string; +} + +export class CometRpcError extends Error { + readonly status: number; + readonly body: unknown; + + constructor(status: number, body: unknown) { + super(`Comet RPC request failed with status ${status}`); + this.name = "CometRpcError"; + this.status = status; + this.body = body; + } +} + +export class CometClient { + constructor( + private readonly baseUrl: string, + private readonly tokenProvider?: TokenProvider, + ) {} + + async listOrgBoards(org_id: number): Promise> { + return this.request>("GET", `/orgs/${encodePathValue(org_id)}/boards`, undefined, true); + } + + async privateMe(): Promise { + return this.request("GET", `/private/me`, undefined, true); + } + + async completeTask(id: number): Promise { + return this.request("POST", `/tasks/${encodePathValue(id)}/complete`, undefined, true); + } + + async createTask(new_task: NewTask): Promise { + return this.request("POST", `/tasks`, new_task, true); + } + + async getTask(id: number): Promise { + return this.request("GET", `/tasks/${encodePathValue(id)}`, undefined, true); + } + + async listTasks(): Promise> { + return this.request>("GET", `/tasks`, undefined, true); + } + + private async request(method: string, path: string, body: unknown, auth: boolean): Promise { + const headers: Record = { accept: "application/json" }; + let requestBody: string | undefined; + if (body !== undefined) { + headers["content-type"] = "application/json"; + requestBody = JSON.stringify(body); + } + if (auth && this.tokenProvider) { + const token = await this.tokenProvider(); + if (token) headers.authorization = `Bearer ${token}`; + } + + const response = await fetch(`${this.baseUrl.replace(/\/$/, "")}${path}`, { + method, + headers, + body: requestBody, + }); + const text = await response.text(); + const payload = parseJsonResponse(text, response.ok); + if (!response.ok) throw new CometRpcError(response.status, payload); + return payload as T; + } +} + +function parseJsonResponse(text: string, ok: boolean): unknown { + if (!text) return undefined; + try { + return JSON.parse(text); + } catch { + if (ok) throw new SyntaxError("Comet RPC response was not valid JSON"); + return text; + } +} + +function encodePathValue(value: string | number | boolean | Array): string { + if (Array.isArray(value)) return value.map((item) => encodeURIComponent(String(item))).join("/"); + return encodeURIComponent(String(value)); +} diff --git a/examples/react-native-rpc/tsconfig.json b/examples/react-native-rpc/tsconfig.json new file mode 100644 index 0000000..ec97d45 --- /dev/null +++ b/examples/react-native-rpc/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "jsx": "react-native", + "lib": ["ES2020", "DOM"], + "module": "ESNext", + "moduleResolution": "Bundler", + "noEmit": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true, + "target": "ES2020" + }, + "include": ["App.tsx", "index.ts", "src/**/*.ts", "src/**/*.tsx"] +}