♻️ 使用 Actix RS 重构 #8
| @@ -5,24 +5,24 @@ on: | ||||
|     branches: [ master ] | ||||
| 
 | ||||
| jobs: | ||||
|   build-docker: | ||||
|     runs-on: edge | ||||
|   build-image: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Checkout | ||||
|         uses: actions/checkout@v3 | ||||
|         uses: actions/checkout@v4 | ||||
|       - name: Set up QEMU | ||||
|         uses: docker/setup-qemu-action@v2 | ||||
|         uses: docker/setup-qemu-action@v3 | ||||
|       - name: Set up Docker Buildx | ||||
|         uses: docker/setup-buildx-action@v2 | ||||
|         uses: docker/setup-buildx-action@v3 | ||||
|       - name: Login to Docker Hub | ||||
|         uses: docker/login-action@v2 | ||||
|         uses: docker/login-action@v3 | ||||
|         with: | ||||
|           username: ${{ secrets.DOCKER_REGISTRY_USERNAME }} | ||||
|           password: ${{ secrets.DOCKER_REGISTRY_TOKEN }} | ||||
|       - name: Build and push | ||||
|         uses: docker/build-push-action@v4 | ||||
|         uses: docker/build-push-action@v5 | ||||
|         with: | ||||
|           context: . | ||||
|           file: ./Dockerfile | ||||
|           push: true | ||||
|           tags: xsheep2010/roadsign:nightly | ||||
|           file: ./Dockerfile | ||||
|           tags: xsheep2010/roadsign:sigma | ||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,4 +1,5 @@ | ||||
| /config | ||||
| /certs | ||||
| /letsencrypt | ||||
|  | ||||
| # Added by cargo | ||||
|   | ||||
							
								
								
									
										1717
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1717
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										21
									
								
								Cargo.toml
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								Cargo.toml
									
									
									
									
									
								
							| @@ -6,24 +6,18 @@ edition = "2021" | ||||
| # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||||
|  | ||||
| [dependencies] | ||||
| config = { version = "0.13.4", features = ["toml"] } | ||||
| futures-util = "0.3.30" | ||||
| http = "1.0.0" | ||||
| hyper-util = { version = "0.1.2", features = ["full"] } | ||||
| actix-files = "0.6.5" | ||||
| actix-proxy = "0.2.0" | ||||
| actix-web = { version = "4.5.1", features = ["rustls-0_22"] } | ||||
| actix-web-httpauth = "0.8.1" | ||||
| awc = "3.4.0" | ||||
| config = { version = "0.14.0", features = ["toml"] } | ||||
| lazy_static = "1.4.0" | ||||
| mime = "0.3.17" | ||||
| percent-encoding = "2.3.1" | ||||
| poem = { version = "2.0.0", features = [ | ||||
|   "tokio-metrics", | ||||
|   "websocket", | ||||
|   "static-files", | ||||
|   "reqwest", | ||||
| ] } | ||||
| poem-openapi = { version = "4.0.0" } | ||||
| queryst = "3.0.0" | ||||
| rand = "0.8.5" | ||||
| regex = "1.10.2" | ||||
| reqwest = { git = "https://github.com/seanmonstar/reqwest.git", branch = "hyper-v1", version = "0.11.23" } | ||||
| serde = "1.0.195" | ||||
| serde_json = "1.0.111" | ||||
| tokio = { version = "1.35.1", features = [ | ||||
| @@ -37,3 +31,6 @@ toml = "0.8.8" | ||||
| tracing = "0.1.40" | ||||
| tracing-subscriber = "0.3.18" | ||||
| wildmatch = "2.3.0" | ||||
| derive_more = "0.99.17" | ||||
| rustls = "0.22.2" | ||||
| rustls-pemfile = "2.0.0" | ||||
|   | ||||
| @@ -9,7 +9,7 @@ WORKDIR /source/pkg/sideload/view | ||||
| RUN npm install | ||||
| RUN npm run build | ||||
| WORKDIR /source | ||||
| RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -buildvcs -o /dist ./pkg/cmd/server/main.go | ||||
| RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -buildvcs -o /dist ./pkg/cmd/server.rs/main.go | ||||
|  | ||||
| # Runtime | ||||
| FROM golang:alpine | ||||
|   | ||||
							
								
								
									
										10
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								README.md
									
									
									
									
									
								
							| @@ -74,9 +74,9 @@ rds cli with this command. | ||||
|  | ||||
| ```shell | ||||
| rds connect <id> <url> <password> | ||||
| # ID will allow you find this server in after commands. | ||||
| # URL is to your roadsign server sideload api. | ||||
| # Password is your roadsign server credential. | ||||
| # ID will allow you find this server.rs in after commands. | ||||
| # URL is to your roadsign server.rs sideload api. | ||||
| # Password is your roadsign server.rs credential. | ||||
| # ====================================================================== | ||||
| # !WARNING! All these things will storage in your $HOME/.roadsignrc.yaml | ||||
| # ====================================================================== | ||||
| @@ -85,8 +85,8 @@ rds connect <id> <url> <password> | ||||
| Then, sync your local config to remote. | ||||
|  | ||||
| ```shell | ||||
| rds sync <server id> <site id> <config file> | ||||
| # Server ID is your server added by last command. | ||||
| rds sync <server.rs id> <site id> <config file> | ||||
| # Server ID is your server.rs added by last command. | ||||
| # Site ID is your new site id or old site id if you need update it. | ||||
| # Config File is your local config file path. | ||||
| ``` | ||||
|   | ||||
| @@ -1,7 +1,17 @@ | ||||
| regions = "./regions" | ||||
| secret = "aEXcED5xJ3" | ||||
|  | ||||
| [listen] | ||||
| proxies = "0.0.0.0:80" | ||||
| proxies_tls = "0.0.0.0:443" | ||||
| sideload = "0.0.0.0:81" | ||||
| [sideload] | ||||
| bind_addr = "0.0.0.0:81" | ||||
|  | ||||
| [[proxies.bind]] | ||||
| addr = "0.0.0.0:80" | ||||
| tls = false | ||||
| [[proxies.bind]] | ||||
| addr = "0.0.0.0:443" | ||||
| tls = false | ||||
|  | ||||
| [[certificates]] | ||||
| domain = "localhost" | ||||
| certs = "certs/fullchain.pem" | ||||
| key = "certs/privkey.pem" | ||||
							
								
								
									
										50
									
								
								src/auth.rs
									
									
									
									
									
								
							
							
						
						
									
										50
									
								
								src/auth.rs
									
									
									
									
									
								
							| @@ -1,50 +0,0 @@ | ||||
| use http::StatusCode; | ||||
| use poem::{ | ||||
|     web::headers::{self, authorization::Basic, HeaderMapExt}, | ||||
|     Endpoint, Error, Middleware, Request, Response, Result, | ||||
| }; | ||||
|  | ||||
| pub struct BasicAuth { | ||||
|     pub username: String, | ||||
|     pub password: String, | ||||
| } | ||||
|  | ||||
| impl<E: Endpoint> Middleware<E> for BasicAuth { | ||||
|     type Output = BasicAuthEndpoint<E>; | ||||
|  | ||||
|     fn transform(&self, ep: E) -> Self::Output { | ||||
|         BasicAuthEndpoint { | ||||
|             ep, | ||||
|             username: self.username.clone(), | ||||
|             password: self.password.clone(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub struct BasicAuthEndpoint<E> { | ||||
|     ep: E, | ||||
|     username: String, | ||||
|     password: String, | ||||
| } | ||||
|  | ||||
| #[poem::async_trait] | ||||
| impl<E: Endpoint> Endpoint for BasicAuthEndpoint<E> { | ||||
|     type Output = E::Output; | ||||
|  | ||||
|     async fn call(&self, req: Request) -> Result<Self::Output> { | ||||
|         if let Some(auth) = req.headers().typed_get::<headers::Authorization<Basic>>() { | ||||
|             if auth.0.username() == self.username && auth.0.password() == self.password { | ||||
|                 return self.ep.call(req).await; | ||||
|             } | ||||
|         } | ||||
|         Err(Error::from_response( | ||||
|             Response::builder() | ||||
|                 .header( | ||||
|                     "WWW-Authenticate", | ||||
|                     "Basic realm=\"RoadSig\", charset=\"UTF-8\"", | ||||
|                 ) | ||||
|                 .status(StatusCode::UNAUTHORIZED) | ||||
|                 .finish(), | ||||
|         )) | ||||
|     } | ||||
| } | ||||
| @@ -7,5 +7,5 @@ use crate::config::loader::load_settings; | ||||
| pub mod loader; | ||||
|  | ||||
| lazy_static! { | ||||
|     pub static ref C: RwLock<Config> = RwLock::new(load_settings()); | ||||
|     pub static ref CFG: RwLock<Config> = RwLock::new(load_settings()); | ||||
| } | ||||
|   | ||||
							
								
								
									
										62
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										62
									
								
								src/main.rs
									
									
									
									
									
								
							| @@ -1,28 +1,28 @@ | ||||
| pub mod auth; | ||||
| extern crate core; | ||||
|  | ||||
| mod config; | ||||
| mod proxies; | ||||
| mod sideload; | ||||
| pub mod warden; | ||||
| mod warden; | ||||
| mod server; | ||||
| pub mod tls; | ||||
|  | ||||
| use std::error; | ||||
| use lazy_static::lazy_static; | ||||
| use poem::{listener::TcpListener, EndpointExt, Route, Server}; | ||||
| use poem_openapi::OpenApiService; | ||||
| use proxies::RoadInstance; | ||||
| use tokio::sync::Mutex; | ||||
| use tokio::task::JoinSet; | ||||
| use tracing::{error, info, Level}; | ||||
|  | ||||
| use crate::proxies::route; | ||||
| use crate::proxies::server::build_proxies; | ||||
| use crate::sideload::server::build_sideload; | ||||
|  | ||||
| lazy_static! { | ||||
|     static ref ROAD: Mutex<RoadInstance> = Mutex::new(RoadInstance::new()); | ||||
| } | ||||
|  | ||||
| #[tokio::main] | ||||
| async fn main() -> Result<(), std::io::Error> { | ||||
| async fn main() -> Result<(), Box<dyn error::Error>> { | ||||
|     // Setting up logging | ||||
|     if std::env::var_os("RUST_LOG").is_none() { | ||||
|         std::env::set_var("RUST_LOG", "poem=debug"); | ||||
|     } | ||||
|     tracing_subscriber::fmt() | ||||
|         .with_max_level(Level::DEBUG) | ||||
|         .init(); | ||||
| @@ -30,11 +30,10 @@ async fn main() -> Result<(), std::io::Error> { | ||||
|     // Prepare all the stuff | ||||
|     info!("Loading proxy regions..."); | ||||
|     match proxies::loader::scan_regions( | ||||
|         config::C | ||||
|         config::CFG | ||||
|             .read() | ||||
|             .await | ||||
|             .get_string("regions") | ||||
|             .unwrap_or("./regions".to_string()), | ||||
|             .get_string("regions")? | ||||
|     ) { | ||||
|         Err(_) => error!("Loading proxy regions... failed"), | ||||
|         Ok((regions, count)) => { | ||||
| @@ -43,37 +42,15 @@ async fn main() -> Result<(), std::io::Error> { | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     let mut server_set = JoinSet::new(); | ||||
|  | ||||
|     // Proxies | ||||
|     let proxies_server = Server::new(TcpListener::bind( | ||||
|         config::C | ||||
|             .read() | ||||
|             .await | ||||
|             .get_string("listen.proxies") | ||||
|             .unwrap_or("0.0.0.0:80".to_string()), | ||||
|     )) | ||||
|     .run(route::handle); | ||||
|     for server in build_proxies().await? { | ||||
|         server_set.spawn(server); | ||||
|     } | ||||
|  | ||||
|     // Sideload | ||||
|     let sideload = OpenApiService::new(sideload::SideloadApi, "Sideload API", "1.0") | ||||
|         .server("http://localhost:3000/cgi"); | ||||
|  | ||||
|     let sideload_server = Server::new(TcpListener::bind( | ||||
|         config::C | ||||
|             .read() | ||||
|             .await | ||||
|             .get_string("listen.sideload") | ||||
|             .unwrap_or("0.0.0.0:81".to_string()), | ||||
|     )) | ||||
|     .run( | ||||
|         Route::new().nest("/cgi", sideload).with(auth::BasicAuth { | ||||
|             username: "RoadSign".to_string(), | ||||
|             password: config::C | ||||
|                 .read() | ||||
|                 .await | ||||
|                 .get_string("secret") | ||||
|                 .unwrap_or("password".to_string()), | ||||
|         }), | ||||
|     ); | ||||
|     server_set.spawn(build_sideload().await?); | ||||
|  | ||||
|     // Process manager | ||||
|     { | ||||
| @@ -85,7 +62,8 @@ async fn main() -> Result<(), std::io::Error> { | ||||
|         app.warden.start().await; | ||||
|     } | ||||
|  | ||||
|     tokio::try_join!(proxies_server, sideload_server)?; | ||||
|     // Wait for web servers | ||||
|     server_set.join_next().await; | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
|   | ||||
| @@ -1,52 +0,0 @@ | ||||
| use std::fmt::Write; | ||||
|  | ||||
| pub struct DirectoryTemplate<'a> { | ||||
|     pub path: &'a str, | ||||
|     pub files: Vec<FileRef>, | ||||
| } | ||||
|  | ||||
| impl<'a> DirectoryTemplate<'a> { | ||||
|     pub fn render(&self) -> String { | ||||
|         let mut s = format!( | ||||
|             r#" | ||||
|         <html> | ||||
|             <head> | ||||
|             <title>Index of {}</title> | ||||
|         </head> | ||||
|         <body> | ||||
|         <h1>Index of /{}</h1> | ||||
|         <ul>"#, | ||||
|             self.path, self.path | ||||
|         ); | ||||
|  | ||||
|         for file in &self.files { | ||||
|             if file.is_dir { | ||||
|                 let _ = write!( | ||||
|                     s, | ||||
|                     r#"<li><a href="{}">{}/</a></li>"#, | ||||
|                     file.url, file.filename | ||||
|                 ); | ||||
|             } else { | ||||
|                 let _ = write!( | ||||
|                     s, | ||||
|                     r#"<li><a href="{}">{}</a></li>"#, | ||||
|                     file.url, file.filename | ||||
|                 ); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         s.push_str( | ||||
|             r#"</ul> | ||||
|         </body> | ||||
|         </html>"#, | ||||
|         ); | ||||
|  | ||||
|         s | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub struct FileRef { | ||||
|     pub url: String, | ||||
|     pub filename: String, | ||||
|     pub is_dir: bool, | ||||
| } | ||||
| @@ -1,6 +1,5 @@ | ||||
| use std::collections::HashMap; | ||||
|  | ||||
| use poem_openapi::Object; | ||||
| use queryst::parse; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| use serde_json::json; | ||||
| @@ -9,14 +8,14 @@ use crate::warden::Application; | ||||
|  | ||||
| use super::responder::StaticResponderConfig; | ||||
|  | ||||
| #[derive(Debug, Object, Clone, Serialize, Deserialize)] | ||||
| #[derive(Debug, Clone, Serialize, Deserialize)] | ||||
| pub struct Region { | ||||
|     pub id: String, | ||||
|     pub locations: Vec<Location>, | ||||
|     pub applications: Vec<Application>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Object, Clone, Serialize, Deserialize)] | ||||
| #[derive(Debug, Clone, Serialize, Deserialize)] | ||||
| pub struct Location { | ||||
|     pub id: String, | ||||
|     pub hosts: Vec<String>, | ||||
| @@ -27,7 +26,7 @@ pub struct Location { | ||||
|     pub destinations: Vec<Destination>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Object, Clone, Serialize, Deserialize)] | ||||
| #[derive(Debug, Clone, Serialize, Deserialize)] | ||||
| pub struct Destination { | ||||
|     pub id: String, | ||||
|     pub uri: String, | ||||
| @@ -75,15 +74,6 @@ impl Destination { | ||||
|         .collect::<Vec<_>>()[0] | ||||
|     } | ||||
|  | ||||
|     pub fn get_websocket_uri(&self) -> Result<String, ()> { | ||||
|         let parts = self.uri.as_str().splitn(2, "://").collect::<Vec<_>>(); | ||||
|         let url = parts.get(1).unwrap_or(&""); | ||||
|         match self.get_protocol() { | ||||
|             "http" | "https" => Ok(url.replace("http", "ws")), | ||||
|             _ => Err(()), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn get_hypertext_uri(&self) -> Result<String, ()> { | ||||
|         match self.get_protocol() { | ||||
|             "http" => Ok("http://".to_string() + self.get_host()), | ||||
|   | ||||
| @@ -1,21 +1,30 @@ | ||||
| use std::collections::VecDeque; | ||||
|  | ||||
| use poem_openapi::Object; | ||||
| use serde::{Deserialize, Serialize}; | ||||
|  | ||||
| use super::config::{Destination, Location, Region}; | ||||
|  | ||||
| #[derive(Debug, Object, Clone, Serialize, Deserialize, PartialEq)] | ||||
| #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] | ||||
| pub struct RoadTrace { | ||||
|     pub region: String, | ||||
|     pub location: String, | ||||
|     pub destination: String, | ||||
|     pub ip_address: String, | ||||
|     pub user_agent: String, | ||||
|     pub error: Option<String>, | ||||
| } | ||||
|  | ||||
| impl RoadTrace { | ||||
|     pub fn from_structs(reg: Region, loc: Location, end: Destination) -> RoadTrace { | ||||
|     pub fn from_structs( | ||||
|         ip: String, | ||||
|         ua: String, | ||||
|         reg: Region, | ||||
|         loc: Location, | ||||
|         end: Destination, | ||||
|     ) -> RoadTrace { | ||||
|         RoadTrace { | ||||
|             ip_address: ip, | ||||
|             user_agent: ua, | ||||
|             region: reg.id, | ||||
|             location: loc.id, | ||||
|             destination: end.id, | ||||
| @@ -24,17 +33,16 @@ impl RoadTrace { | ||||
|     } | ||||
|  | ||||
|     pub fn from_structs_with_error( | ||||
|         ip: String, | ||||
|         ua: String, | ||||
|         reg: Region, | ||||
|         loc: Location, | ||||
|         end: Destination, | ||||
|         err: String, | ||||
|     ) -> RoadTrace { | ||||
|         RoadTrace { | ||||
|             region: reg.id, | ||||
|             location: loc.id, | ||||
|             destination: end.id, | ||||
|             error: Some(err), | ||||
|         } | ||||
|         let mut trace = Self::from_structs(ip, ua, reg, loc, end); | ||||
|         trace.error = Some(err); | ||||
|         return trace; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -47,7 +55,7 @@ pub struct RoadMetrics { | ||||
|     pub recent_errors: VecDeque<RoadTrace>, | ||||
| } | ||||
|  | ||||
| const MAX_TRACE_COUNT: usize = 10; | ||||
| const MAX_TRACE_COUNT: usize = 32; | ||||
|  | ||||
| impl RoadMetrics { | ||||
|     pub fn new() -> RoadMetrics { | ||||
| @@ -67,26 +75,35 @@ impl RoadMetrics { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn add_success_request(&mut self, reg: Region, loc: Location, end: Destination) { | ||||
|     pub fn add_success_request( | ||||
|         &mut self, | ||||
|         ip: String, | ||||
|         ua: String, | ||||
|         reg: Region, | ||||
|         loc: Location, | ||||
|         end: Destination, | ||||
|     ) { | ||||
|         self.requests_count += 1; | ||||
|         self.recent_successes | ||||
|             .push_back(RoadTrace::from_structs(reg, loc, end)); | ||||
|             .push_back(RoadTrace::from_structs(ip, ua, reg, loc, end)); | ||||
|         if self.recent_successes.len() > MAX_TRACE_COUNT { | ||||
|             self.recent_successes.pop_front(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn add_faliure_request( | ||||
|     pub fn add_failure_request( | ||||
|         &mut self, | ||||
|         ip: String, | ||||
|         ua: String, | ||||
|         reg: Region, | ||||
|         loc: Location, | ||||
|         end: Destination, | ||||
|         err: String, // For some reason error is rarely clonable, so we use preformatted message | ||||
|         err: String, // For some reason error is rarely cloneable, so we use preformatted message | ||||
|     ) { | ||||
|         self.requests_count += 1; | ||||
|         self.failures_count += 1; | ||||
|         self.recent_errors | ||||
|             .push_back(RoadTrace::from_structs_with_error(reg, loc, end, err)); | ||||
|             .push_back(RoadTrace::from_structs_with_error(ip, ua, reg, loc, end, err)); | ||||
|         if self.recent_errors.len() > MAX_TRACE_COUNT { | ||||
|             self.recent_errors.pop_front(); | ||||
|         } | ||||
|   | ||||
| @@ -1,7 +1,9 @@ | ||||
| use http::Method; | ||||
| use poem::http::{HeaderMap, Uri}; | ||||
| use actix_web::http::header::{ContentType, HeaderMap}; | ||||
| use actix_web::http::{Method, StatusCode, Uri}; | ||||
| use regex::Regex; | ||||
| use wildmatch::WildMatch; | ||||
| use actix_web::{error, HttpResponse}; | ||||
| use derive_more::{Display}; | ||||
|  | ||||
| use crate::warden::WardenInstance; | ||||
|  | ||||
| @@ -10,12 +12,52 @@ use self::{ | ||||
|     metrics::RoadMetrics, | ||||
| }; | ||||
|  | ||||
| pub mod browser; | ||||
| pub mod config; | ||||
| pub mod loader; | ||||
| pub mod metrics; | ||||
| pub mod responder; | ||||
| pub mod route; | ||||
| pub mod server; | ||||
|  | ||||
| #[derive(Debug, Display)] | ||||
| pub enum ProxyError { | ||||
|     #[display(fmt = "Remote gateway issue")] | ||||
|     BadGateway, | ||||
|  | ||||
|     #[display(fmt = "No configured able to process this request")] | ||||
|     NoGateway, | ||||
|  | ||||
|     #[display(fmt = "Not found")] | ||||
|     NotFound, | ||||
|  | ||||
|     #[display(fmt = "Only accepts method GET")] | ||||
|     MethodGetOnly, | ||||
|  | ||||
|     #[display(fmt = "Invalid request path")] | ||||
|     InvalidRequestPath, | ||||
|  | ||||
|     #[display(fmt = "Upstream does not support protocol you used")] | ||||
|     NotImplemented, | ||||
| } | ||||
|  | ||||
| impl error::ResponseError for ProxyError { | ||||
|     fn status_code(&self) -> StatusCode { | ||||
|         match *self { | ||||
|             ProxyError::BadGateway => StatusCode::BAD_GATEWAY, | ||||
|             ProxyError::NoGateway => StatusCode::NOT_FOUND, | ||||
|             ProxyError::NotFound => StatusCode::NOT_FOUND, | ||||
|             ProxyError::MethodGetOnly => StatusCode::METHOD_NOT_ALLOWED, | ||||
|             ProxyError::InvalidRequestPath => StatusCode::BAD_REQUEST, | ||||
|             ProxyError::NotImplemented => StatusCode::NOT_IMPLEMENTED, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn error_response(&self) -> HttpResponse { | ||||
|         HttpResponse::build(self.status_code()) | ||||
|             .insert_header(ContentType::html()) | ||||
|             .body(self.to_string()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone)] | ||||
| pub struct RoadInstance { | ||||
| @@ -38,7 +80,7 @@ impl RoadInstance { | ||||
|     pub fn filter( | ||||
|         &self, | ||||
|         uri: &Uri, | ||||
|         method: Method, | ||||
|         method: &Method, | ||||
|         headers: &HeaderMap, | ||||
|     ) -> Option<(&Region, &Location)> { | ||||
|         self.regions.iter().find_map(|region| { | ||||
|   | ||||
| @@ -1,117 +1,51 @@ | ||||
| use futures_util::{SinkExt, StreamExt}; | ||||
| use http::{header, request::Builder, HeaderMap, Method, StatusCode, Uri}; | ||||
| use lazy_static::lazy_static; | ||||
| use poem::{ | ||||
|     web::{websocket::WebSocket, StaticFileRequest}, | ||||
|     Body, Error, FromRequest, IntoResponse, Request, Response, | ||||
| }; | ||||
| use std::{ | ||||
|     ffi::OsStr, | ||||
|     path::{Path, PathBuf}, | ||||
|     sync::Arc, | ||||
| }; | ||||
| use tokio::sync::RwLock; | ||||
| use tokio_tungstenite::connect_async; | ||||
|  | ||||
| use super::browser::{DirectoryTemplate, FileRef}; | ||||
|  | ||||
| lazy_static! { | ||||
|     pub static ref CLIENT: reqwest::Client = reqwest::Client::new(); | ||||
| } | ||||
|  | ||||
| pub async fn repond_websocket(req: Builder, ws: WebSocket) -> Response { | ||||
|     ws.on_upgrade(move |socket| async move { | ||||
|         let (mut clientsink, mut clientstream) = socket.split(); | ||||
|  | ||||
|         // Start connection to server | ||||
|         let (serversocket, _) = connect_async(req.body(()).unwrap()).await.unwrap(); | ||||
|         let (mut serversink, mut serverstream) = serversocket.split(); | ||||
|  | ||||
|         let client_live = Arc::new(RwLock::new(true)); | ||||
|         let server_live = client_live.clone(); | ||||
|  | ||||
|         tokio::spawn(async move { | ||||
|             while let Some(Ok(msg)) = clientstream.next().await { | ||||
|                 if (serversink.send(msg.into()).await).is_err() { | ||||
|                     break; | ||||
|                 }; | ||||
|                 if !*client_live.read().await { | ||||
|                     break; | ||||
|                 }; | ||||
|             } | ||||
|  | ||||
|             *client_live.write().await = false; | ||||
|         }); | ||||
|  | ||||
|         // Relay server messages to the client | ||||
|         tokio::spawn(async move { | ||||
|             while let Some(Ok(msg)) = serverstream.next().await { | ||||
|                 if (clientsink.send(msg.into()).await).is_err() { | ||||
|                     break; | ||||
|                 }; | ||||
|                 if !*server_live.read().await { | ||||
|                     break; | ||||
|                 }; | ||||
|             } | ||||
|  | ||||
|             *server_live.write().await = false; | ||||
|         }); | ||||
|     }) | ||||
|     .into_response() | ||||
| } | ||||
| use actix_files::{NamedFile}; | ||||
| use actix_proxy::IntoHttpResponse; | ||||
| use actix_web::{HttpRequest, HttpResponse, web}; | ||||
| use actix_web::http::Method; | ||||
| use awc::Client; | ||||
| use tracing::log::warn; | ||||
| use crate::proxies::ProxyError; | ||||
|  | ||||
| pub async fn respond_hypertext( | ||||
|     uri: String, | ||||
|     ori: &Uri, | ||||
|     req: &Request, | ||||
|     method: Method, | ||||
|     body: Body, | ||||
|     headers: &HeaderMap, | ||||
| ) -> Result<Response, Error> { | ||||
|     let ip = req.remote_addr().to_string(); | ||||
|     req: HttpRequest, | ||||
|     client: web::Data<Client>, | ||||
| ) -> Result<HttpResponse, ProxyError> { | ||||
|     let ip = req.peer_addr().unwrap().ip().to_string(); | ||||
|     let proto = req.uri().scheme_str().unwrap(); | ||||
|     let host = req.uri().host().unwrap(); | ||||
|  | ||||
|     let mut headers = headers.clone(); | ||||
|     headers.insert("Server", "RoadSign".parse().unwrap()); | ||||
|     headers.insert("X-Forward-For", ip.parse().unwrap()); | ||||
|     headers.insert("X-Forwarded-Proto", proto.parse().unwrap()); | ||||
|     headers.insert("X-Forwarded-Host", host.parse().unwrap()); | ||||
|     headers.insert("X-Real-IP", ip.parse().unwrap()); | ||||
|     let mut headers = req.headers().clone(); | ||||
|     headers.insert("Server".parse().unwrap(), "RoadSign".parse().unwrap()); | ||||
|     headers.insert("X-Forward-For".parse().unwrap(), ip.parse().unwrap()); | ||||
|     headers.insert("X-Forwarded-Proto".parse().unwrap(), proto.parse().unwrap()); | ||||
|     headers.insert("X-Forwarded-Host".parse().unwrap(), host.parse().unwrap()); | ||||
|     headers.insert("X-Real-IP".parse().unwrap(), ip.parse().unwrap()); | ||||
|     headers.insert( | ||||
|         "Forwarded", | ||||
|         "Forwarded".parse().unwrap(), | ||||
|         format!("by={};for={};host={};proto={}", ip, ip, host, proto) | ||||
|             .parse() | ||||
|             .unwrap(), | ||||
|     ); | ||||
|  | ||||
|     let res = CLIENT | ||||
|         .request(method, uri + ori.path() + ori.query().unwrap_or("")) | ||||
|         .headers(headers.clone()) | ||||
|         .body(body.into_bytes().await.unwrap()) | ||||
|         .send() | ||||
|         .await; | ||||
|     let res = client.get(uri).send().await; | ||||
|  | ||||
|     match res { | ||||
|     return match res { | ||||
|         Ok(result) => { | ||||
|             let mut res = Response::default(); | ||||
|             res.extensions().clone_from(&result.extensions()); | ||||
|             result.headers().iter().for_each(|(key, val)| { | ||||
|                 res.headers_mut().insert(key, val.to_owned()); | ||||
|             }); | ||||
|             res.headers_mut() | ||||
|                 .insert("Server", "RoadSign".parse().unwrap()); | ||||
|             res.set_status(result.status()); | ||||
|             res.set_version(result.version()); | ||||
|             res.set_body(result.bytes().await.unwrap()); | ||||
|             let mut res = result.into_http_response(); | ||||
|             res.headers_mut().insert("Server".parse().unwrap(), "RoadSign".parse().unwrap()); | ||||
|             Ok(res) | ||||
|         } | ||||
|  | ||||
|         Err(error) => Err(Error::from_string( | ||||
|             error.to_string(), | ||||
|             error.status().unwrap_or(StatusCode::BAD_GATEWAY), | ||||
|         )), | ||||
|         Err(error) => { | ||||
|             warn!("Proxy got a upstream issue... {:?}", error); | ||||
|             Err(ProxyError::BadGateway) | ||||
|         } | ||||
|     }; | ||||
| } | ||||
|  | ||||
| pub struct StaticResponderConfig { | ||||
| @@ -126,14 +60,10 @@ pub struct StaticResponderConfig { | ||||
|  | ||||
| pub async fn respond_static( | ||||
|     cfg: StaticResponderConfig, | ||||
|     method: Method, | ||||
|     req: &Request, | ||||
| ) -> Result<Response, Error> { | ||||
|     if method != Method::GET { | ||||
|         return Err(Error::from_string( | ||||
|             "This destination only support GET request.", | ||||
|             StatusCode::METHOD_NOT_ALLOWED, | ||||
|         )); | ||||
|     req: HttpRequest, | ||||
| ) -> Result<HttpResponse, ProxyError> { | ||||
|     if req.method() != Method::GET { | ||||
|         return Err(ProxyError::MethodGetOnly); | ||||
|     } | ||||
|  | ||||
|     let path = req | ||||
| @@ -142,9 +72,12 @@ pub async fn respond_static( | ||||
|         .trim_start_matches('/') | ||||
|         .trim_end_matches('/'); | ||||
|  | ||||
|     let path = percent_encoding::percent_decode_str(path) | ||||
|         .decode_utf8() | ||||
|         .map_err(|_| Error::from_status(StatusCode::NOT_FOUND))?; | ||||
|     let path = match percent_encoding::percent_decode_str(path).decode_utf8() { | ||||
|         Ok(val) => val, | ||||
|         Err(_) => { | ||||
|             return Err(ProxyError::NotFound); | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     let base_path = cfg.uri.parse::<PathBuf>().unwrap(); | ||||
|     let mut file_path = base_path.clone(); | ||||
| @@ -159,7 +92,7 @@ pub async fn respond_static( | ||||
|     } | ||||
|  | ||||
|     if !file_path.starts_with(cfg.uri) { | ||||
|         return Err(Error::from_status(StatusCode::FORBIDDEN)); | ||||
|         return Err(ProxyError::InvalidRequestPath); | ||||
|     } | ||||
|  | ||||
|     if !file_path.exists() { | ||||
| @@ -172,87 +105,30 @@ pub async fn respond_static( | ||||
|             file_path.pop(); | ||||
|             file_path.push((file_name + &suffix).as_str()); | ||||
|             if file_path.is_file() { | ||||
|                 return Ok(StaticFileRequest::from_request_without_body(req) | ||||
|                     .await? | ||||
|                     .create_response(&file_path, cfg.utf8)? | ||||
|                     .into_response()); | ||||
|                 return Ok(NamedFile::open(file_path).unwrap().into_response(&req)); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if let Some(file) = cfg.fallback { | ||||
|             let fallback_path = base_path.join(file); | ||||
|             if fallback_path.is_file() { | ||||
|                 return Ok(StaticFileRequest::from_request_without_body(req) | ||||
|                     .await? | ||||
|                     .create_response(&fallback_path, cfg.utf8)? | ||||
|                     .into_response()); | ||||
|                 return Ok(NamedFile::open(fallback_path).unwrap().into_response(&req)); | ||||
|             } | ||||
|         } | ||||
|         return Err(Error::from_status(StatusCode::NOT_FOUND)); | ||||
|     } | ||||
|  | ||||
|     if file_path.is_file() { | ||||
|         Ok(StaticFileRequest::from_request_without_body(req) | ||||
|             .await? | ||||
|             .create_response(&file_path, cfg.utf8)? | ||||
|             .into_response()) | ||||
|         return Err(ProxyError::NotFound); | ||||
|     } | ||||
|  | ||||
|     return if file_path.is_file() { | ||||
|         Ok(NamedFile::open(file_path).unwrap().into_response(&req)) | ||||
|     } else { | ||||
|         if cfg.with_slash | ||||
|             && !req.original_uri().path().ends_with('/') | ||||
|             && (cfg.index.is_some() || cfg.browse) | ||||
|         { | ||||
|             let redirect_to = format!("{}/", req.original_uri().path()); | ||||
|             return Ok(Response::builder() | ||||
|                 .status(StatusCode::FOUND) | ||||
|                 .header(header::LOCATION, redirect_to) | ||||
|                 .finish()); | ||||
|         } | ||||
|  | ||||
|         if let Some(index_file) = &cfg.index { | ||||
|             let index_path = file_path.join(index_file); | ||||
|             if index_path.is_file() { | ||||
|                 return Ok(StaticFileRequest::from_request_without_body(req) | ||||
|                     .await? | ||||
|                     .create_response(&index_path, cfg.utf8)? | ||||
|                     .into_response()); | ||||
|                 return Ok(NamedFile::open(index_path).unwrap().into_response(&req)); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if cfg.browse { | ||||
|             let read_dir = file_path | ||||
|                 .read_dir() | ||||
|                 .map_err(|_| Error::from_status(StatusCode::FORBIDDEN))?; | ||||
|             let mut template = DirectoryTemplate { | ||||
|                 path: &path, | ||||
|                 files: Vec::new(), | ||||
|         return Err(ProxyError::NotFound); | ||||
|     }; | ||||
|  | ||||
|             for res in read_dir { | ||||
|                 let entry = res.map_err(|_| Error::from_status(StatusCode::FORBIDDEN))?; | ||||
|  | ||||
|                 if let Some(filename) = entry.file_name().to_str() { | ||||
|                     let mut base_url = req.original_uri().path().to_string(); | ||||
|                     if !base_url.ends_with('/') { | ||||
|                         base_url.push('/'); | ||||
|                     } | ||||
|                     let filename_url = percent_encoding::percent_encode( | ||||
|                         filename.as_bytes(), | ||||
|                         percent_encoding::NON_ALPHANUMERIC, | ||||
|                     ); | ||||
|                     template.files.push(FileRef { | ||||
|                         url: format!("{base_url}{filename_url}"), | ||||
|                         filename: filename.to_string(), | ||||
|                         is_dir: entry.path().is_dir(), | ||||
|                     }); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             let html = template.render(); | ||||
|             Ok(Response::builder() | ||||
|                 .header(header::CONTENT_TYPE, mime::TEXT_HTML_UTF_8.as_ref()) | ||||
|                 .body(Body::from_string(html))) | ||||
|         } else { | ||||
|             Err(Error::from_status(StatusCode::NOT_FOUND)) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,10 +1,6 @@ | ||||
| use http::Method; | ||||
| use poem::{ | ||||
|     handler, | ||||
|     http::{HeaderMap, StatusCode, Uri}, | ||||
|     web::websocket::WebSocket, | ||||
|     Body, Error, FromRequest, IntoResponse, Request, Response, Result, | ||||
| }; | ||||
| use actix_web::{HttpRequest, HttpResponse, ResponseError, web}; | ||||
| use actix_web::http::header; | ||||
| use awc::Client; | ||||
| use rand::seq::SliceRandom; | ||||
|  | ||||
| use crate::{ | ||||
| @@ -14,23 +10,14 @@ use crate::{ | ||||
|     }, | ||||
|     ROAD, | ||||
| }; | ||||
| use crate::proxies::ProxyError; | ||||
|  | ||||
| #[handler] | ||||
| pub async fn handle( | ||||
|     req: &Request, | ||||
|     uri: &Uri, | ||||
|     headers: &HeaderMap, | ||||
|     method: Method, | ||||
|     body: Body, | ||||
| ) -> Result<impl IntoResponse, Error> { | ||||
| pub async fn handle(req: HttpRequest, client: web::Data<Client>) -> HttpResponse { | ||||
|     let readable_app = ROAD.lock().await; | ||||
|     let (region, location) = match readable_app.filter(uri, method.clone(), headers) { | ||||
|     let (region, location) = match readable_app.filter(req.uri(), req.method(), req.headers()) { | ||||
|         Some(val) => val, | ||||
|         None => { | ||||
|             return Err(Error::from_string( | ||||
|                 "There are no region be able to respone this request.", | ||||
|                 StatusCode::NOT_FOUND, | ||||
|             )) | ||||
|             return ProxyError::NoGateway.error_response(); | ||||
|         } | ||||
|     }; | ||||
|  | ||||
| @@ -41,58 +28,26 @@ pub async fn handle( | ||||
|  | ||||
|     async fn forward( | ||||
|         end: &Destination, | ||||
|         req: &Request, | ||||
|         ori: &Uri, | ||||
|         headers: &HeaderMap, | ||||
|         method: Method, | ||||
|         body: Body, | ||||
|     ) -> Result<Response, Error> { | ||||
|         // Handle websocket | ||||
|         if let Ok(ws) = WebSocket::from_request_without_body(req).await { | ||||
|             // Get uri | ||||
|             let Ok(uri) = end.get_websocket_uri() else { | ||||
|                 return Err(Error::from_string( | ||||
|                     "This destination was not support websockets.", | ||||
|                     StatusCode::NOT_IMPLEMENTED, | ||||
|                 )); | ||||
|             }; | ||||
|  | ||||
|             // Build request | ||||
|             let mut ws_req = http::Request::builder().uri(&uri); | ||||
|             for (key, value) in headers.iter() { | ||||
|                 ws_req = ws_req.header(key, value); | ||||
|             } | ||||
|  | ||||
|             // Start the websocket connection | ||||
|             return Ok(responder::repond_websocket(ws_req, ws).await); | ||||
|         } | ||||
|  | ||||
|         req: HttpRequest, | ||||
|         client: web::Data<Client>, | ||||
|     ) -> Result<HttpResponse, ProxyError> { | ||||
|         // Handle normal web request | ||||
|         match end.get_type() { | ||||
|             DestinationType::Hypertext => { | ||||
|                 let Ok(uri) = end.get_hypertext_uri() else { | ||||
|                     return Err(Error::from_string( | ||||
|                         "This destination was not support web requests.", | ||||
|                         StatusCode::NOT_IMPLEMENTED, | ||||
|                     )); | ||||
|                     return Err(ProxyError::NotImplemented); | ||||
|                 }; | ||||
|  | ||||
|                 responder::respond_hypertext(uri, ori, req, method, body, headers).await | ||||
|                 responder::respond_hypertext(uri, req, client).await | ||||
|             } | ||||
|             DestinationType::StaticFiles => { | ||||
|                 let Ok(cfg) = end.get_static_config() else { | ||||
|                     return Err(Error::from_string( | ||||
|                         "This destination was not support static files.", | ||||
|                         StatusCode::NOT_IMPLEMENTED, | ||||
|                     )); | ||||
|                     return Err(ProxyError::NotImplemented); | ||||
|                 }; | ||||
|  | ||||
|                 responder::respond_static(cfg, method, req).await | ||||
|                 responder::respond_static(cfg, req).await | ||||
|             } | ||||
|             _ => Err(Error::from_string( | ||||
|                 "Unsupported destination protocol.", | ||||
|                 StatusCode::NOT_IMPLEMENTED, | ||||
|             )), | ||||
|             _ => Err(ProxyError::NotImplemented) | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -100,23 +55,32 @@ pub async fn handle( | ||||
|     let loc = location.clone(); | ||||
|     let end = destination.clone(); | ||||
|  | ||||
|     match forward(&end, req, uri, headers, method, body).await { | ||||
|     let ip = match req.peer_addr() { | ||||
|         None => "unknown".to_string(), | ||||
|         Some(val) => val.ip().to_string() | ||||
|     }; | ||||
|     let ua = match req.headers().get(header::USER_AGENT) { | ||||
|         None => "unknown".to_string(), | ||||
|         Some(val) => val.to_str().unwrap().to_string(), | ||||
|     }; | ||||
|  | ||||
|     match forward(&end, req, client).await { | ||||
|         Ok(resp) => { | ||||
|             tokio::spawn(async move { | ||||
|                 let writable_app = &mut ROAD.lock().await; | ||||
|                 writable_app.metrics.add_success_request(reg, loc, end); | ||||
|                 writable_app.metrics.add_success_request(ip, ua, reg, loc, end); | ||||
|             }); | ||||
|             Ok(resp) | ||||
|             resp | ||||
|         } | ||||
|         Err(err) => { | ||||
|             let message = format!("{:}", err); | ||||
|         Err(resp) => { | ||||
|             let message = resp.to_string(); | ||||
|             tokio::spawn(async move { | ||||
|                 let writable_app = &mut ROAD.lock().await; | ||||
|                 writable_app | ||||
|                     .metrics | ||||
|                     .add_faliure_request(reg, loc, end, message); | ||||
|                     .add_failure_request(ip, ua, reg, loc, end, message); | ||||
|             }); | ||||
|             Err(err) | ||||
|             resp.error_response() | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										39
									
								
								src/proxies/server.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/proxies/server.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| use std::error; | ||||
| use actix_web::{App, HttpServer, web}; | ||||
| use actix_web::dev::Server; | ||||
| use actix_web::middleware::Logger; | ||||
| use awc::Client; | ||||
| use crate::config::CFG; | ||||
| use crate::proxies::route; | ||||
| use crate::server::ServerBindConfig; | ||||
| use crate::tls::{load_certificates, use_rustls}; | ||||
|  | ||||
| pub async fn build_proxies() -> Result<Vec<Server>, Box<dyn error::Error>> { | ||||
|     load_certificates().await?; | ||||
|  | ||||
|     let cfg = CFG | ||||
|         .read() | ||||
|         .await | ||||
|         .get::<Vec<ServerBindConfig>>("proxies.bind")?; | ||||
|  | ||||
|     let mut tasks = Vec::new(); | ||||
|     for item in cfg { | ||||
|         tasks.push(build_single_proxy(item)?); | ||||
|     } | ||||
|  | ||||
|     Ok(tasks) | ||||
| } | ||||
|  | ||||
| pub fn build_single_proxy(cfg: ServerBindConfig) -> Result<Server, Box<dyn error::Error>> { | ||||
|     let server = HttpServer::new(|| { | ||||
|         App::new() | ||||
|             .wrap(Logger::default()) | ||||
|             .app_data(web::Data::new(Client::default())) | ||||
|             .route("/", web::to(route::handle)) | ||||
|     }); | ||||
|     if cfg.tls { | ||||
|         Ok(server.bind_rustls_0_22(cfg.addr, use_rustls()?)?.run()) | ||||
|     } else { | ||||
|         Ok(server.bind(cfg.addr)?.run()) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										7
									
								
								src/server.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/server.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| use serde::{Deserialize, Serialize}; | ||||
|  | ||||
| #[derive(Serialize, Deserialize)] | ||||
| pub struct ServerBindConfig { | ||||
|     pub addr: String, | ||||
|     pub tls: bool, | ||||
| } | ||||
| @@ -1,19 +1,15 @@ | ||||
| use poem_openapi::OpenApi; | ||||
| use actix_web::{Scope, web}; | ||||
| use crate::sideload::overview::get_overview; | ||||
| use crate::sideload::regions::list_region; | ||||
|  | ||||
| pub mod overview; | ||||
| pub mod regions; | ||||
| mod overview; | ||||
| mod regions; | ||||
| pub mod server; | ||||
|  | ||||
| pub struct SideloadApi; | ||||
| static ROOT: &str = ""; | ||||
|  | ||||
| #[OpenApi] | ||||
| impl SideloadApi { | ||||
|     #[oai(path = "/", method = "get")] | ||||
|     async fn index(&self) -> overview::OverviewResponse { | ||||
|         overview::index().await | ||||
|     } | ||||
|  | ||||
|     #[oai(path = "/regions", method = "get")] | ||||
|     async fn regions_index(&self) -> regions::RegionResponse { | ||||
|         regions::index().await | ||||
|     } | ||||
| pub fn service() -> Scope { | ||||
|     web::scope("/cgi") | ||||
|         .route(ROOT, web::get().to(get_overview)) | ||||
|         .route("/regions", web::get().to(list_region)) | ||||
| } | ||||
| @@ -1,46 +1,23 @@ | ||||
| use poem_openapi::{payload::Json, ApiResponse, Object}; | ||||
| use actix_web::web; | ||||
| use serde::Serialize; | ||||
| use crate::proxies::config::{Destination, Location}; | ||||
| use crate::proxies::metrics::RoadTrace; | ||||
| use crate::ROAD; | ||||
|  | ||||
| use crate::{ | ||||
|     proxies::{ | ||||
|         config::{Destination, Location}, | ||||
|         metrics::RoadTrace, | ||||
|     }, | ||||
|     ROAD, | ||||
| }; | ||||
|  | ||||
| #[derive(ApiResponse)] | ||||
| pub enum OverviewResponse { | ||||
|     /// Return the overview data. | ||||
|     #[oai(status = 200)] | ||||
|     Ok(Json<OverviewData>), | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Object, Clone, PartialEq)] | ||||
| #[derive(Debug, Clone, PartialEq, Serialize)] | ||||
| pub struct OverviewData { | ||||
|     /// Loaded regions count | ||||
|     #[oai(read_only)] | ||||
|     regions: usize, | ||||
|     /// Loaded locations count | ||||
|     #[oai(read_only)] | ||||
|     locations: usize, | ||||
|     /// Loaded destnations count | ||||
|     #[oai(read_only)] | ||||
|     destinations: usize, | ||||
|     /// Recent requests count | ||||
|     requests_count: u64, | ||||
|     /// Recent requests success count | ||||
|     faliures_count: u64, | ||||
|     /// Recent requests falied count | ||||
|     failures_count: u64, | ||||
|     successes_count: u64, | ||||
|     /// Recent requests success rate | ||||
|     success_rate: f64, | ||||
|     /// Recent successes | ||||
|     recent_successes: Vec<RoadTrace>, | ||||
|     /// Recent errors | ||||
|     recent_errors: Vec<RoadTrace>, | ||||
| } | ||||
|  | ||||
| pub async fn index() -> OverviewResponse { | ||||
| pub async fn get_overview() -> web::Json<OverviewData> { | ||||
|     let locked_app = ROAD.lock().await; | ||||
|     let regions = locked_app.regions.clone(); | ||||
|     let locations = regions | ||||
| @@ -51,13 +28,13 @@ pub async fn index() -> OverviewResponse { | ||||
|         .iter() | ||||
|         .flat_map(|item| item.destinations.clone()) | ||||
|         .collect::<Vec<Destination>>(); | ||||
|     OverviewResponse::Ok(Json(OverviewData { | ||||
|     web::Json(OverviewData { | ||||
|         regions: regions.len(), | ||||
|         locations: locations.len(), | ||||
|         destinations: destinations.len(), | ||||
|         requests_count: locked_app.metrics.requests_count, | ||||
|         successes_count: locked_app.metrics.requests_count - locked_app.metrics.failures_count, | ||||
|         faliures_count: locked_app.metrics.failures_count, | ||||
|         failures_count: locked_app.metrics.failures_count, | ||||
|         success_rate: locked_app.metrics.get_success_rate(), | ||||
|         recent_successes: locked_app | ||||
|             .metrics | ||||
| @@ -71,5 +48,5 @@ pub async fn index() -> OverviewResponse { | ||||
|             .clone() | ||||
|             .into_iter() | ||||
|             .collect::<Vec<_>>(), | ||||
|     })) | ||||
|     }) | ||||
| } | ||||
| @@ -1,25 +1,9 @@ | ||||
| use poem_openapi::{payload::Json, ApiResponse}; | ||||
| use actix_web::web; | ||||
| use crate::proxies::config::Region; | ||||
| use crate::ROAD; | ||||
|  | ||||
| use crate::{proxies::config::Region, ROAD}; | ||||
|  | ||||
| #[derive(ApiResponse)] | ||||
| pub enum RegionResponse { | ||||
|     /// Return the region data. | ||||
|     #[oai(status = 200)] | ||||
|     Ok(Json<Region>), | ||||
|     /// Return the list of region data. | ||||
|     #[oai(status = 200)] | ||||
|     OkMany(Json<Vec<Region>>), | ||||
|     /// Return the region data after created. | ||||
|     #[oai(status = 201)] | ||||
|     Created(Json<Region>), | ||||
|     /// Return was not found. | ||||
|     #[oai(status = 404)] | ||||
|     NotFound, | ||||
| } | ||||
|  | ||||
| pub async fn index() -> RegionResponse { | ||||
| pub async fn list_region() -> web::Json<Vec<Region>> { | ||||
|     let locked_app = ROAD.lock().await; | ||||
|  | ||||
|     RegionResponse::OkMany(Json(locked_app.regions.clone())) | ||||
|     web::Json(locked_app.regions.clone()) | ||||
| } | ||||
							
								
								
									
										35
									
								
								src/sideload/server.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/sideload/server.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| use std::error; | ||||
| use actix_web::dev::Server; | ||||
| use actix_web::{App, HttpServer}; | ||||
| use actix_web_httpauth::extractors::AuthenticationError; | ||||
| use actix_web_httpauth::headers::www_authenticate::basic::Basic; | ||||
| use actix_web_httpauth::middleware::HttpAuthentication; | ||||
| use crate::sideload; | ||||
|  | ||||
| pub async fn build_sideload() -> Result<Server, Box<dyn error::Error>> { | ||||
|     Ok( | ||||
|         HttpServer::new(|| { | ||||
|             App::new() | ||||
|                 .wrap(HttpAuthentication::basic(|req, credentials| async move { | ||||
|                     let password = match crate::config::CFG | ||||
|                         .read() | ||||
|                         .await | ||||
|                         .get_string("secret") { | ||||
|                         Ok(val) => val, | ||||
|                         Err(_) => return Err((AuthenticationError::new(Basic::new()).into(), req)) | ||||
|                     }; | ||||
|                     if credentials.password().unwrap_or("") != password { | ||||
|                         Err((AuthenticationError::new(Basic::new()).into(), req)) | ||||
|                     } else { | ||||
|                         Ok(req) | ||||
|                     } | ||||
|                 })) | ||||
|                 .service(sideload::service()) | ||||
|         }).bind( | ||||
|             crate::config::CFG | ||||
|                 .read() | ||||
|                 .await | ||||
|                 .get_string("sideload.bind_addr")? | ||||
|         )?.workers(1).run() | ||||
|     ) | ||||
| } | ||||
							
								
								
									
										76
									
								
								src/tls.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								src/tls.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,76 @@ | ||||
| use std::fs::File; | ||||
| use std::{error}; | ||||
| use std::io::BufReader; | ||||
| use std::sync::Arc; | ||||
| use config::ConfigError; | ||||
| use lazy_static::lazy_static; | ||||
| use rustls::crypto::ring::sign::RsaSigningKey; | ||||
| use rustls::server::{ClientHello, ResolvesServerCert}; | ||||
| use rustls::sign::CertifiedKey; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| use std::sync::Mutex; | ||||
| use wildmatch::WildMatch; | ||||
|  | ||||
| lazy_static! { | ||||
|     static ref CERTS: Mutex<Vec<CertificateConfig>> = Mutex::new(Vec::new()); | ||||
| } | ||||
|  | ||||
| #[derive(Debug)] | ||||
| struct ProxyCertResolver; | ||||
|  | ||||
| impl ResolvesServerCert for ProxyCertResolver { | ||||
|     fn resolve(&self, handshake: ClientHello) -> Option<Arc<CertifiedKey>> { | ||||
|         let domain = handshake.server_name()?; | ||||
|  | ||||
|         let certs = CERTS.lock().unwrap(); | ||||
|         for cert in certs.iter() { | ||||
|             if WildMatch::new(cert.domain.as_str()).matches(domain) { | ||||
|                 return match cert.clone().load() { | ||||
|                     Ok(val) => Some(val), | ||||
|                     Err(_) => None | ||||
|                 }; | ||||
|             } | ||||
|         } | ||||
|         None | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Serialize, Deserialize)] | ||||
| struct CertificateConfig { | ||||
|     pub domain: String, | ||||
|     pub certs: String, | ||||
|     pub key: String, | ||||
| } | ||||
|  | ||||
| impl CertificateConfig { | ||||
|     pub fn load(self) -> Result<Arc<CertifiedKey>, Box<dyn error::Error>> { | ||||
|         let certs = | ||||
|             rustls_pemfile::certs(&mut BufReader::new(&mut File::open(self.certs)?)) | ||||
|                 .collect::<Result<Vec<_>, _>>()?; | ||||
|         let key = | ||||
|             rustls_pemfile::private_key(&mut BufReader::new(&mut File::open(self.key)?))? | ||||
|                 .unwrap(); | ||||
|         let sign = RsaSigningKey::new(&key)?; | ||||
|  | ||||
|         Ok(Arc::new(CertifiedKey::new(certs, Arc::new(sign)))) | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub async fn load_certificates() -> Result<(), ConfigError> { | ||||
|     let certs = crate::config::CFG | ||||
|         .read() | ||||
|         .await | ||||
|         .get::<Vec<CertificateConfig>>("certificates")?; | ||||
|  | ||||
|     CERTS.lock().unwrap().clone_from(&certs); | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| pub fn use_rustls() -> Result<rustls::ServerConfig, ConfigError> { | ||||
|     Ok( | ||||
|         rustls::ServerConfig::builder() | ||||
|             .with_no_client_auth() | ||||
|             .with_cert_resolver(Arc::new(ProxyCertResolver)) | ||||
|     ) | ||||
| } | ||||
| @@ -2,10 +2,9 @@ pub mod runner; | ||||
|  | ||||
| use std::collections::HashMap; | ||||
|  | ||||
| use futures_util::lock::Mutex; | ||||
| use lazy_static::lazy_static; | ||||
| use poem_openapi::Object; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| use tokio::sync::Mutex; | ||||
| use tracing::{debug, warn}; | ||||
|  | ||||
| use crate::proxies::config::Region; | ||||
| @@ -63,7 +62,7 @@ impl Default for WardenInstance { | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Object, Clone, Serialize, Deserialize)] | ||||
| #[derive(Debug, Clone, Serialize, Deserialize)] | ||||
| pub struct Application { | ||||
|     pub id: String, | ||||
|     pub exe: String, | ||||
|   | ||||
| @@ -1,12 +1,12 @@ | ||||
| use std::{borrow::BorrowMut, collections::HashMap, io}; | ||||
|  | ||||
| use super::Application; | ||||
| use futures_util::lock::Mutex; | ||||
| use lazy_static::lazy_static; | ||||
| use tokio::{ | ||||
|     io::{AsyncBufReadExt, BufReader}, | ||||
|     process::{Child, Command}, | ||||
| }; | ||||
| use tokio::sync::Mutex; | ||||
|  | ||||
| lazy_static! { | ||||
|     static ref STDOUT: Mutex<HashMap<String, String>> = Mutex::new(HashMap::new()); | ||||
|   | ||||
							
								
								
									
										179
									
								
								test/data/warden/dist/client/_astro/Media.7FWSwaPB.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										179
									
								
								test/data/warden/dist/client/_astro/Media.7FWSwaPB.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										1
									
								
								test/data/warden/dist/client/_astro/Media.Co8_pG1j.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								test/data/warden/dist/client/_astro/Media.Co8_pG1j.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										1
									
								
								test/data/warden/dist/client/_astro/_slug_.bcjV8AoT.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								test/data/warden/dist/client/_astro/_slug_.bcjV8AoT.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| :root{--bs-body-font-family: "IBM Plex Serif", "Noto Serif SC", sans-serif !important}html,body{font-family:var(--bs-body-font-family)}@font-face{font-display:swap;font-family:"IBM Plex Serif";font-style:normal;font-weight:100;src:url(/_astro/ibm-plex-serif-v19-latin-100.6qNbweSL.woff2) format("woff2")}@font-face{font-display:swap;font-family:"IBM Plex Serif";font-style:italic;font-weight:100;src:url(/_astro/ibm-plex-serif-v19-latin-100italic.E22nrI7z.woff2) format("woff2")}@font-face{font-display:swap;font-family:"IBM Plex Serif";font-style:normal;font-weight:200;src:url(/_astro/ibm-plex-serif-v19-latin-200.GFXE_YJc.woff2) format("woff2")}@font-face{font-display:swap;font-family:"IBM Plex Serif";font-style:italic;font-weight:200;src:url(/_astro/ibm-plex-serif-v19-latin-200italic.pJK4yaaG.woff2) format("woff2")}@font-face{font-display:swap;font-family:"IBM Plex Serif";font-style:normal;font-weight:300;src:url(/_astro/ibm-plex-serif-v19-latin-300.RVbRgkxX.woff2) format("woff2")}@font-face{font-display:swap;font-family:"IBM Plex Serif";font-style:italic;font-weight:300;src:url(/_astro/ibm-plex-serif-v19-latin-300italic.ZdSVgmcR.woff2) format("woff2")}@font-face{font-display:swap;font-family:"IBM Plex Serif";font-style:normal;font-weight:400;src:url(/_astro/ibm-plex-serif-v19-latin-regular.HRmMD3sQ.woff2) format("woff2")}@font-face{font-display:swap;font-family:"IBM Plex Serif";font-style:italic;font-weight:400;src:url(/_astro/ibm-plex-serif-v19-latin-italic.MiJiQVsi.woff2) format("woff2")}@font-face{font-display:swap;font-family:"IBM Plex Serif";font-style:normal;font-weight:500;src:url(/_astro/ibm-plex-serif-v19-latin-500.xAA_w-Ac.woff2) format("woff2")}@font-face{font-display:swap;font-family:"IBM Plex Serif";font-style:italic;font-weight:500;src:url(/_astro/ibm-plex-serif-v19-latin-500italic.Unq84pJ7.woff2) format("woff2")}@font-face{font-display:swap;font-family:"IBM Plex Serif";font-style:normal;font-weight:600;src:url(/_astro/ibm-plex-serif-v19-latin-600.cuuqzllG.woff2) format("woff2")}@font-face{font-display:swap;font-family:"IBM Plex Serif";font-style:italic;font-weight:600;src:url(/_astro/ibm-plex-serif-v19-latin-600italic.vDhUog1q.woff2) format("woff2")}@font-face{font-display:swap;font-family:"IBM Plex Serif";font-style:normal;font-weight:700;src:url(/_astro/ibm-plex-serif-v19-latin-700.yX9JjmCp.woff2) format("woff2")}@font-face{font-display:swap;font-family:"IBM Plex Serif";font-style:italic;font-weight:700;src:url(/_astro/ibm-plex-serif-v19-latin-700italic.QM1RA0vx.woff2) format("woff2")}@font-face{font-display:swap;font-family:"Noto Serif SC";font-style:normal;font-weight:200;src:url(/_astro/noto-serif-sc-v22-chinese-simplified-200.g4OBZhIi.woff2) format("woff2")}@font-face{font-display:swap;font-family:"Noto Serif SC";font-style:normal;font-weight:300;src:url(/_astro/noto-serif-sc-v22-chinese-simplified-300.yFtdUYoh.woff2) format("woff2")}@font-face{font-display:swap;font-family:"Noto Serif SC";font-style:normal;font-weight:400;src:url(/_astro/noto-serif-sc-v22-chinese-simplified-regular.9muiKgFz.woff2) format("woff2")}@font-face{font-display:swap;font-family:"Noto Serif SC";font-style:normal;font-weight:500;src:url(/_astro/noto-serif-sc-v22-chinese-simplified-500.exkAspFQ.woff2) format("woff2")}@font-face{font-display:swap;font-family:"Noto Serif SC";font-style:normal;font-weight:600;src:url(/_astro/noto-serif-sc-v22-chinese-simplified-600.4n6uFOXj.woff2) format("woff2")}@font-face{font-display:swap;font-family:"Noto Serif SC";font-style:normal;font-weight:700;src:url(/_astro/noto-serif-sc-v22-chinese-simplified-700.HyiB9Pzv.woff2) format("woff2")}@font-face{font-display:swap;font-family:"Noto Serif SC";font-style:normal;font-weight:900;src:url(/_astro/noto-serif-sc-v22-chinese-simplified-900.ERSRy_0V.woff2) format("woff2")}.astro-route-announcer{position:absolute;left:0;top:0;clip:rect(0 0 0 0);-webkit-clip-path:inset(50%);clip-path:inset(50%);overflow:hidden;white-space:nowrap;width:1px;height:1px}.h-fullpage{height:calc(100vh - 64px)}.max-h-fullpage{max-height:calc(100vh - 64px)}.mt-header{margin-top:64px}.top-header{top:64px}html{overflow-x:hidden!important;overflow-y:auto!important}@keyframes astroFadeInOut{0%{opacity:1}to{opacity:0}}@keyframes astroFadeIn{0%{opacity:0}}@keyframes astroFadeOut{to{opacity:0}}@keyframes astroSlideFromRight{0%{transform:translate(100%)}}@keyframes astroSlideFromLeft{0%{transform:translate(-100%)}}@keyframes astroSlideToRight{to{transform:translate(100%)}}@keyframes astroSlideToLeft{to{transform:translate(-100%)}}@media (prefers-reduced-motion){::view-transition-group(*),::view-transition-old(*),::view-transition-new(*){animation:none!important}[data-astro-transition-scope]{animation:none!important}} | ||||
							
								
								
									
										1
									
								
								test/data/warden/dist/client/_astro/_slug_.yOjdTrIk.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								test/data/warden/dist/client/_astro/_slug_.yOjdTrIk.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										24
									
								
								test/data/warden/dist/client/_astro/client.olTvLX7Y.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								test/data/warden/dist/client/_astro/client.olTvLX7Y.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										1
									
								
								test/data/warden/dist/client/_astro/hoisted.l-JsOPk0.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								test/data/warden/dist/client/_astro/hoisted.l-JsOPk0.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								test/data/warden/dist/client/_astro/ibm-plex-serif-v19-latin-100.6qNbweSL.woff2
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								test/data/warden/dist/client/_astro/ibm-plex-serif-v19-latin-100.6qNbweSL.woff2
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								test/data/warden/dist/client/_astro/ibm-plex-serif-v19-latin-100italic.E22nrI7z.woff2
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								test/data/warden/dist/client/_astro/ibm-plex-serif-v19-latin-100italic.E22nrI7z.woff2
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								test/data/warden/dist/client/_astro/ibm-plex-serif-v19-latin-200.GFXE_YJc.woff2
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								test/data/warden/dist/client/_astro/ibm-plex-serif-v19-latin-200.GFXE_YJc.woff2
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								test/data/warden/dist/client/_astro/ibm-plex-serif-v19-latin-200italic.pJK4yaaG.woff2
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								test/data/warden/dist/client/_astro/ibm-plex-serif-v19-latin-200italic.pJK4yaaG.woff2
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								test/data/warden/dist/client/_astro/ibm-plex-serif-v19-latin-300.RVbRgkxX.woff2
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								test/data/warden/dist/client/_astro/ibm-plex-serif-v19-latin-300.RVbRgkxX.woff2
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								test/data/warden/dist/client/_astro/ibm-plex-serif-v19-latin-300italic.ZdSVgmcR.woff2
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								test/data/warden/dist/client/_astro/ibm-plex-serif-v19-latin-300italic.ZdSVgmcR.woff2
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								test/data/warden/dist/client/_astro/ibm-plex-serif-v19-latin-500.xAA_w-Ac.woff2
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								test/data/warden/dist/client/_astro/ibm-plex-serif-v19-latin-500.xAA_w-Ac.woff2
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								test/data/warden/dist/client/_astro/ibm-plex-serif-v19-latin-500italic.Unq84pJ7.woff2
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								test/data/warden/dist/client/_astro/ibm-plex-serif-v19-latin-500italic.Unq84pJ7.woff2
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								test/data/warden/dist/client/_astro/ibm-plex-serif-v19-latin-600.cuuqzllG.woff2
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								test/data/warden/dist/client/_astro/ibm-plex-serif-v19-latin-600.cuuqzllG.woff2
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								test/data/warden/dist/client/_astro/ibm-plex-serif-v19-latin-600italic.vDhUog1q.woff2
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								test/data/warden/dist/client/_astro/ibm-plex-serif-v19-latin-600italic.vDhUog1q.woff2
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								test/data/warden/dist/client/_astro/ibm-plex-serif-v19-latin-700.yX9JjmCp.woff2
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								test/data/warden/dist/client/_astro/ibm-plex-serif-v19-latin-700.yX9JjmCp.woff2
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								test/data/warden/dist/client/_astro/ibm-plex-serif-v19-latin-700italic.QM1RA0vx.woff2
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								test/data/warden/dist/client/_astro/ibm-plex-serif-v19-latin-700italic.QM1RA0vx.woff2
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								test/data/warden/dist/client/_astro/ibm-plex-serif-v19-latin-italic.MiJiQVsi.woff2
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								test/data/warden/dist/client/_astro/ibm-plex-serif-v19-latin-italic.MiJiQVsi.woff2
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								test/data/warden/dist/client/_astro/ibm-plex-serif-v19-latin-regular.HRmMD3sQ.woff2
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								test/data/warden/dist/client/_astro/ibm-plex-serif-v19-latin-regular.HRmMD3sQ.woff2
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										9
									
								
								test/data/warden/dist/client/_astro/index.LFf77hJu.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								test/data/warden/dist/client/_astro/index.LFf77hJu.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								test/data/warden/dist/client/_astro/noto-serif-sc-v22-chinese-simplified-200.g4OBZhIi.woff2
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								test/data/warden/dist/client/_astro/noto-serif-sc-v22-chinese-simplified-200.g4OBZhIi.woff2
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								test/data/warden/dist/client/_astro/noto-serif-sc-v22-chinese-simplified-300.yFtdUYoh.woff2
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								test/data/warden/dist/client/_astro/noto-serif-sc-v22-chinese-simplified-300.yFtdUYoh.woff2
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								test/data/warden/dist/client/_astro/noto-serif-sc-v22-chinese-simplified-500.exkAspFQ.woff2
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								test/data/warden/dist/client/_astro/noto-serif-sc-v22-chinese-simplified-500.exkAspFQ.woff2
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								test/data/warden/dist/client/_astro/noto-serif-sc-v22-chinese-simplified-600.4n6uFOXj.woff2
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								test/data/warden/dist/client/_astro/noto-serif-sc-v22-chinese-simplified-600.4n6uFOXj.woff2
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								test/data/warden/dist/client/_astro/noto-serif-sc-v22-chinese-simplified-700.HyiB9Pzv.woff2
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								test/data/warden/dist/client/_astro/noto-serif-sc-v22-chinese-simplified-700.HyiB9Pzv.woff2
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								test/data/warden/dist/client/_astro/noto-serif-sc-v22-chinese-simplified-900.ERSRy_0V.woff2
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								test/data/warden/dist/client/_astro/noto-serif-sc-v22-chinese-simplified-900.ERSRy_0V.woff2
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								test/data/warden/dist/client/_astro/noto-serif-sc-v22-chinese-simplified-regular.9muiKgFz.woff2
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								test/data/warden/dist/client/_astro/noto-serif-sc-v22-chinese-simplified-regular.9muiKgFz.woff2
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										2
									
								
								test/data/warden/dist/client/admin/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								test/data/warden/dist/client/admin/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| index.html | ||||
| assets/ | ||||
							
								
								
									
										21
									
								
								test/data/warden/dist/client/favicon.svg
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								test/data/warden/dist/client/favicon.svg
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| <svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" width="1024" height="1024"> | ||||
| 	<title>SmartSheep Logo</title> | ||||
| 	<defs> | ||||
| 		<image  width="124" height="198" id="img1" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHwAAADGCAMAAAAnkRSfAAAAAXNSR0IB2cksfwAAAq9QTFRFAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////J9QBRAAAAOV0Uk5TAA0WEiFfjMLMr40/BRdmtuz//vbQYAQmpfLdQAGovyURj/iOD0r07loDGJfbPDTf/bhd94QLdPrrVQLWcPmtHU31egknzehRg9Ev1xnzbgYHhfzmTMorSOSbFJloKsniReXDKG/7kQ6m8WMw3ue7I4cKNdTZOFTqsR5/Hzra0zZY7al4Ir5SPWHvnpbG4MRrlKJs4UfjvKos3Eu1DICGCBq0XjHV6YqCG7lZM89cEJJ72EQ5WztxJC1OfGl+T315dnVzkHKVbZ9no2ViwFZDNzIuKSAcFfBGy4tQd7Onk0K3PqxTmt0ZReEAAAZZSURBVHic7dzpX1RVGAfwyyKMyHYuyDqgjLKICjcIBodNhEoJFAY0wCBEJyTCBWUx0WRJU1wqlKIwTLDEUoMiyFJssWyjtBAr2/1DGvywzzPDeXGehzf8/oHv594z985znnPOlaTRWFlbW9tIMxLbOXb2KtVch3mO5LSTs4srG47s5j7fg9b29PJmY5F9fNWEtrUfmxTZfwGZvTBAw6ZEs2gxER4YNNU2XnxwyBIKO3SpqT3ML1tOgIeFgzhjykO26HiEtxmcsciHo5DxaK1ZnLGYFbqZwxmLDcPE4+It4kxJSMTDVwJP2uQkrUrGwlMemQ5n7NHHkIY+avX0OGNrUnH01Md5dK1XGooe58ajs/S16xDwjEw9l86yshH+bNUOlp/1scjrN4jXpSdy+HSmz80Tr1tvnOZdMxbvJxHqrPwChZN/qlC8rg7cxKmzos3i+XVbpn3VjsTwdLF4Pm2rgZMveQahzip1lzn5Z33F67rsMk5d2bZdPJ+8info43fsFM8nzuV84bLgCIQ/2/JdnEMv764Qr+sqqzh5pXqPeN4qM5Lz3kc+lyGe3+vCPfQhCH+2Nft4n/r9z4vXdQdqOXl9Xah4HphBm4lbPUJPpeEF3qGPOSheVx/y4X3qVaXieccXD3NevOEIQj8tr5G3zkqPQ6izPI/yPnZZ2eJ1te8xTl05ni+ezzhRwslrX7ISzzu9zDm/YN6vINRZ+et5h76pULyuPnmK96lvxqizotM5Lz7eDqGflfYqd4ntjDD0r63h1FnLPPG6+nXe2ZXyBkKd1Xo6iZPXvtkmnj/zFu/M9vBZhDqrvYNTZx014nld5TlOXUlAqLMWv83XzWJM8w5CiX2+k7vOuoAwu3o3lnd6816Y+KH3uOjKefF6L4R+1qXLvCV20vsYsyt/zqde7uoWr0sfLOO8+KAPEXRHZ84SOx5hemGcXeXy1VlJGNdunF0d53rs3J1QdKknnIPXX8TBpbaPzK8UjqUICZckm+lbmUGIuwR6C6a593IfHi6pu5ss6x8j4pK084rFVuYnqLhxYp1g4Y17Ghk3TqxVZof+U3RcWnLVXIl9DR83ltiZcJ11hQI3TqxbILyfBpfsILyeCHeA8OtE+CII/4wID4Dwz4nwLyD8SyL8MoTHEeE3IHwLEd4I4V8R4Rsh/AQRngvh2H+po/kawm8S4d9A+LdEeB2Ef0eEJ0D4fCK8GsLnEOEuEB5BhDdDOM5U0TTfQ3gIEV4E4QeI8KMQ/gMRXgDhlUS4CsJROiNAwJWBq0T4fgjHnaWOJxbCERY9wewGbDmQCIeag/JJItwHwjF6oFCgBok8QIUDHQIZYUsbmBwAVw4R4VnQbe8hwqH9fXINEQ4tfyk/EuEx0Jgj7KYC4wr92suJ8GBozH8iwqHFD307EQ7tcFFuzSCux9grD0QN4rdpcB20u8TgSYN7QH13Qy8NngFtrtA20ODJUNvZ8DMNHgVt39X+QoMXQ3g8zpEgk9hAS+vxCPs3oAyC+CAN7gThGqJj16HQsrbmEg1+HsLdWmnwRBBHOA8BZS+0iScJYacYlAZoPTud6Jz9dhAnOWotSSnQbQ8iOmOfD60ll9DY0oaZxPug236HCC+FfnC1RHg7dNup8DAIjyHCK6CtC1T48pnEhyC8ighfDj1q4UR4KlTJ3CXCbaGGkBcRLvmb2soFKvym6X1vQjhSDKfNdKnhV9zPZ0xMxNSpYixVX8KYtoDJNWTVEJ0tSbqAiae+an+jtI2pbxnh5aBmqoWlsXjY/H73lEbj1nTvIMLhdY409Pb2LpwReTazmc1sZjMbsdm8IsDPb2v/ENIhEktJi1ZphmerSvA9sqp5NIkTPhPk/QdR63MkNV2TZgx1RB3AB0nbN2W+4keI/zl1khxJtTNKkspNV+87EU4Lwrlh2h0IQjgfDSZvl4nN5NVE+G1oRXEbEV4BNQE7iPBCqCGUQ4SvhPBzRPhfEF5GhA9AeBYRDl45Fd4NNX43EeEDEN5EhPdA7c8uIrwP2jlgT4TvgdqfR4jw4r9NbQPVSRIp2vTlXkbWBUwx3eycS9eSuj51ETv4HzJbOtM5+SXndo2yfL1lP1GPDMD4dqz55P873ng+toNop8hYBs9W33lA+ywtJP5A/XAW/Nd//37j2gFreno4Ua2treOj/T+0HjP//7ac7AAAAABJRU5ErkJggg=="/> | ||||
| 		<image  width="122" height="142" id="img2" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHoAAACOCAMAAADJhOzZAAAAAXNSR0IB2cksfwAAAppQTFRFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1tr9yQAAAN50Uk5TAAkMHUtzmZp0UaTn/6ZSIOrrnRqo/P2qC/qf9PZTG7O4UPL1VASO/psHwdHc7BXo+CIF+y0BOuNI1lfGarGEl6V7vJxi10oGMRQcKrcK7k7zAtt9u6uR02Eh6TZB7+08zbU/4kBjET4vMxBvON+yMAOtJQhr+fcPiSvhWTXVHoPD0q6BwHB+ualmTJ4oH/BJRN11vxdnIzQZ5ZCjiMTUis7eVn+TGEPI5mh85Cd6bNqVXiRa2YLgR1wTW4sWN24OmL1yMk9gDSmhlstk8cKNZbCicXaFhml5vl/JXaynVit8GwAABu9JREFUeJztm/tDk1UYx88GCbLDZcDmGIoKjImATubENAhNJW9cLDU10kQhVGhqYGYMxUzNrLRMtMRLoRheyGtWhlqZl+x+Mav/pSkou3y3Pefddvyl7+/f97Nz3rPnPOd5zstYCKRSq9WqUDxIVBGRj/TrFxUdGSEbrOofo+F3pYmNkzvy+KgEfl/axHiJ5KRkHe+TfkCSNLIhKoW7Smc0yELHpXJ3DRwkiZw2mHtqSJoc9NB0L3RGphSyKcvshebDTDLQ2cO9yTxHLQWdC9B5I6SgRwK0ZZQMdL4WoK2jZaBtCQBtHiMDzQoQeqwU9KMAzcdJQY9H6MfEn6Mg1ShE6CJRcERktHiq8ThCG8XAqv6xvalGsUiqMQGhJwqR45/o+4dqh9FTjUkIPVmEnDRA72LVJ5NTjSkInVBCJxuMVjdvShQ11XgS7FzcMpWOHpThYU6NIzqnWQBaN51MTpvh5R5MTDVmlgK0uYyMzvTMrzgvH0qzVsxCL/spKtk0DPzwLGKq8TRCz6ai1TnAPTybZp6D0HOp6BFo080lop9BS7yUGpTmoVU6n4heoAfmFGowHm0F7tJ8mjkpBZjNzxLRC3TAXWmjmbOfA2Y+gYheiF5XHtFsW4TQzxPdixFaSzSzRISuIs7ZEoROoKKXwiheTTPXoEVKHvULyK2vpZkXBoVeNhDN+HKaeQVCl1LRFXUIXU8zv4jQ6XYqeyVCF9C8q1BIyVhNRb+E0CnLSN4khG4grlHGatES5zUkb3UjsFqnUdFrkJ2/TPKqyoFVv5aKtuUidA7Ja0dhWE8/LsYidCopsbS9AqzmKWT0OvSy9a+SvE3oZ9OPiw608/Fmknc9stKPi3aUlfINpB2kBVk3ktHsNeS3rKFY4XExkY7ehPycVFt4HTk309HTUSTmWyjWrchJTmidSVIlekAp5dD3BnJqBI6L8GWbFxCccOtqFDguwh2EbyM401BCq3uTjt6ONiCeR5i3mW+h+XqbjmZVcMbfCWys2IGcOwXQ/eCMv0twotIyTxZA18JY2vheYGcUMjYJoO2oQkwqoO1CvvSZAmwjRFcFTvBWIJ/ufQH0bhjQrIHT8dYGZBRpn5j2wGHvDWgsQWVt/oEAmn0I0fsqAhphgbdOBN0Ko0pba0DjfuQj5tK9OoAecTAweiycrkMi6DiUoTUFRreiKM73iaCzUTGoPjDahHoY3EI+/NzVR97DNn9M8G2BMy7USWjXePkPUpKkTIjWEMtI92SL9hp2DMU/AtXOuDlSAM2m1nvYZx0m/eTJcNg7RIbNHEfczB1HaUXDTyCaLxVBs4j1LqG8cxexjOSAYZxXijWfjh1v6zm2WusKy6gzVjEXDztGCM1KVp0oPHDyZGFXmYFcEWGfwuIA15HP2fdlOOWUkAPn8c6VJhTJlQmevDi91BqEHLD25gxJQuFUmYoxuuF0+NHz8P+Lrww/GteXnZm8hGtkUz2bfD06I+MG29mHh1597mFNOGPn0dkpSmj7UqoKEFf2fCaDzFi+90l5xgU5aFbrWautXCeJzNjn7uzyL76UhmYXO93IEjauPi2endGT55hTq77qlkl2RrUpl5o687QbLl85Ju1mZp8MXzslH/u/Qi1Vd3e3SHkoZHL0P1scU3zlm+2ywepvL/dcgNFsO3RVKvni+L76QZ5RyoXPXn3nVlC0XiN3D4OWw6P6bOmScr3XqVFeZ5tK+h2poGTr8E5BrkuZ8qtt3mTesUQC2XYDkLn1poScbyIic/0cgZ6SQiVDMjeHH+2DzPnecKNhc+PehG+ilzyUyBaL6yROfU+7SaFUqlu+wJxfDmt+b8KFwHvShfVQU4u6hve1KIxfh5QUwb5br8oVvWnaDfb2Gz4XmFN66s1gV0WU7RwwZMi147V+w2B+M+wrPNAP4mBV5paexpTZsmOjz5al6cc8v2D+kzg5/pLrzR1r2wlEb98I22YuGk6sGbso6WfPlaM7t3eMS4pnW33sl0X+p9qpHHGyYQ6srDUWTOz69ejNm8v3158ph71Nd9WJk1lkoIkkqYPQ1fZUGu7NC0qrYMysBt3QElWlwO2LBzIthV0hMSUoGTPL/81feKJJo6xUkP87YfX6Vy75rqQHuihY9C1Fs+2UfVLAWOFX5kTiRXugi7nBkFNErth46oKvxgxF84MrL9fgaxgUbW4PisxYlsK3nfpHkGDGqrcpWeTmkOSeaqP4uEeOC00CaDoNvyfyrdT9oavYtP9ZQJ/1gddDW9K2H77t/RUWkFlbpDBw+lP3X0dwT6xPDXf+DlehRh3ZMt9Xjq9PvxPtCBO3R/ZTW1vqMnRue6m+sfN280IpBXx79drzW892xcxu+edfY1b0oN2tApvEf5wd39VVwSN7AAAAAElFTkSuQmCC"/> | ||||
| 	</defs> | ||||
| 	<style> | ||||
| 		.s0 { fill: #ffffff;stroke: #000000;stroke-miterlimit:100;stroke-width: 56 }  | ||||
| 		.s1 { fill: #4750a3;stroke: #000000;stroke-miterlimit:100;stroke-width: 56 }  | ||||
| 	</style> | ||||
| 	<path id="Wool" fill-rule="evenodd" class="s0" d="m128 608.4c0 95.9 77.4 173.6 172.8 173.6h441.6c84.8 0 153.6-69.1 153.6-154.3 0-74.6-52.8-136.9-122.9-151.1 4.9-12.9 7.7-27 7.7-41.7 0-63.9-51.6-115.8-115.2-115.8-23.6 0-45.7 7.3-64 19.6-33.2-57.9-95.2-96.7-166.4-96.7-106.1 0-192 86.3-192 192.9 0 3.2 0.1 6.5 0.2 9.7-67.2 23.8-115.4 88.1-115.4 163.8z"/> | ||||
| 	<g id="Crystal"> | ||||
| 		<path id="Crystal" class="s1" d="m699 224l138.6 80v160l-138.6 80-138.6-80v-160z"/> | ||||
| 		<use id="Highlight" href="#img1" x="688" y="255"/> | ||||
| 	</g> | ||||
| 	<g id="Horn"> | ||||
| 	</g> | ||||
| 	<g id="Face"> | ||||
| 		<use id="Slime" href="#img2" x="233" y="538"/> | ||||
| 	</g> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 8.1 KiB | 
							
								
								
									
										
											BIN
										
									
								
								test/data/warden/dist/client/media/nicolas-saintot-xkFhOdId7mA-unsplash.jpg
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								test/data/warden/dist/client/media/nicolas-saintot-xkFhOdId7mA-unsplash.jpg
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 1.3 MiB | 
							
								
								
									
										1
									
								
								test/data/warden/dist/client/sitemap-0.xml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								test/data/warden/dist/client/sitemap-0.xml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1"><url><loc>https://smartsheep.studio/</loc></url><url><loc>https://smartsheep.studio/events/</loc></url><url><loc>https://smartsheep.studio/posts/</loc></url></urlset> | ||||
							
								
								
									
										1
									
								
								test/data/warden/dist/client/sitemap-index.xml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								test/data/warden/dist/client/sitemap-index.xml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?><sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"><sitemap><loc>https://smartsheep.studio/sitemap-0.xml</loc></sitemap></sitemapindex> | ||||
							
								
								
									
										3
									
								
								test/data/warden/dist/server/_empty-middleware.mjs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								test/data/warden/dist/server/_empty-middleware.mjs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| const onRequest = undefined; | ||||
|  | ||||
| export { onRequest }; | ||||
							
								
								
									
										6
									
								
								test/data/warden/dist/server/chunks/_slug__3BAY271A.mjs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								test/data/warden/dist/server/chunks/_slug__3BAY271A.mjs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| export { renderers } from '../renderers.mjs'; | ||||
| export { onRequest } from '../_empty-middleware.mjs'; | ||||
|  | ||||
| const page = () => import('./pages/_slug__TUDhKBhQ.mjs').then(n => n.c); | ||||
|  | ||||
| export { page }; | ||||
							
								
								
									
										6
									
								
								test/data/warden/dist/server/chunks/_slug__EgGcJ0nJ.mjs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								test/data/warden/dist/server/chunks/_slug__EgGcJ0nJ.mjs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| export { renderers } from '../renderers.mjs'; | ||||
| export { onRequest } from '../_empty-middleware.mjs'; | ||||
|  | ||||
| const page = () => import('./pages/_slug__TUDhKBhQ.mjs').then(n => n._); | ||||
|  | ||||
| export { page }; | ||||
							
								
								
									
										6
									
								
								test/data/warden/dist/server/chunks/_slug__wxGnmrgA.mjs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								test/data/warden/dist/server/chunks/_slug__wxGnmrgA.mjs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| export { renderers } from '../renderers.mjs'; | ||||
| export { onRequest } from '../_empty-middleware.mjs'; | ||||
|  | ||||
| const page = () => import('./pages/_slug__TUDhKBhQ.mjs').then(n => n.d); | ||||
|  | ||||
| export { page }; | ||||
							
								
								
									
										342
									
								
								test/data/warden/dist/server/chunks/astro/assets-service_4dMyVCFm.mjs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										342
									
								
								test/data/warden/dist/server/chunks/astro/assets-service_4dMyVCFm.mjs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,342 @@ | ||||
| import { isRemotePath, joinPaths } from '@astrojs/internal-helpers/path'; | ||||
| import { A as AstroError, E as ExpectedImage, L as LocalImageUsedWrongly, M as MissingImageDimension, U as UnsupportedImageFormat, I as IncompatibleDescriptorOptions, a as UnsupportedImageConversion, b as MissingSharp } from '../astro_5WdVqH1c.mjs'; | ||||
|  | ||||
| const VALID_SUPPORTED_FORMATS = [ | ||||
|   "jpeg", | ||||
|   "jpg", | ||||
|   "png", | ||||
|   "tiff", | ||||
|   "webp", | ||||
|   "gif", | ||||
|   "svg", | ||||
|   "avif" | ||||
| ]; | ||||
| const DEFAULT_OUTPUT_FORMAT = "webp"; | ||||
| const DEFAULT_HASH_PROPS = ["src", "width", "height", "format", "quality"]; | ||||
|  | ||||
| function isESMImportedImage(src) { | ||||
|   return typeof src === "object"; | ||||
| } | ||||
| function isRemoteImage(src) { | ||||
|   return typeof src === "string"; | ||||
| } | ||||
|  | ||||
| function matchPattern(url, remotePattern) { | ||||
|   return matchProtocol(url, remotePattern.protocol) && matchHostname(url, remotePattern.hostname, true) && matchPort(url, remotePattern.port) && matchPathname(url, remotePattern.pathname, true); | ||||
| } | ||||
| function matchPort(url, port) { | ||||
|   return !port || port === url.port; | ||||
| } | ||||
| function matchProtocol(url, protocol) { | ||||
|   return !protocol || protocol === url.protocol.slice(0, -1); | ||||
| } | ||||
| function matchHostname(url, hostname, allowWildcard) { | ||||
|   if (!hostname) { | ||||
|     return true; | ||||
|   } else if (!allowWildcard || !hostname.startsWith("*")) { | ||||
|     return hostname === url.hostname; | ||||
|   } else if (hostname.startsWith("**.")) { | ||||
|     const slicedHostname = hostname.slice(2); | ||||
|     return slicedHostname !== url.hostname && url.hostname.endsWith(slicedHostname); | ||||
|   } else if (hostname.startsWith("*.")) { | ||||
|     const slicedHostname = hostname.slice(1); | ||||
|     const additionalSubdomains = url.hostname.replace(slicedHostname, "").split(".").filter(Boolean); | ||||
|     return additionalSubdomains.length === 1; | ||||
|   } | ||||
|   return false; | ||||
| } | ||||
| function matchPathname(url, pathname, allowWildcard) { | ||||
|   if (!pathname) { | ||||
|     return true; | ||||
|   } else if (!allowWildcard || !pathname.endsWith("*")) { | ||||
|     return pathname === url.pathname; | ||||
|   } else if (pathname.endsWith("/**")) { | ||||
|     const slicedPathname = pathname.slice(0, -2); | ||||
|     return slicedPathname !== url.pathname && url.pathname.startsWith(slicedPathname); | ||||
|   } else if (pathname.endsWith("/*")) { | ||||
|     const slicedPathname = pathname.slice(0, -1); | ||||
|     const additionalPathChunks = url.pathname.replace(slicedPathname, "").split("/").filter(Boolean); | ||||
|     return additionalPathChunks.length === 1; | ||||
|   } | ||||
|   return false; | ||||
| } | ||||
| function isRemoteAllowed(src, { | ||||
|   domains = [], | ||||
|   remotePatterns = [] | ||||
| }) { | ||||
|   if (!isRemotePath(src)) | ||||
|     return false; | ||||
|   const url = new URL(src); | ||||
|   return domains.some((domain) => matchHostname(url, domain)) || remotePatterns.some((remotePattern) => matchPattern(url, remotePattern)); | ||||
| } | ||||
|  | ||||
| function isLocalService(service) { | ||||
|   if (!service) { | ||||
|     return false; | ||||
|   } | ||||
|   return "transform" in service; | ||||
| } | ||||
| function parseQuality(quality) { | ||||
|   let result = parseInt(quality); | ||||
|   if (Number.isNaN(result)) { | ||||
|     return quality; | ||||
|   } | ||||
|   return result; | ||||
| } | ||||
| const baseService = { | ||||
|   propertiesToHash: DEFAULT_HASH_PROPS, | ||||
|   validateOptions(options) { | ||||
|     if (!options.src || typeof options.src !== "string" && typeof options.src !== "object") { | ||||
|       throw new AstroError({ | ||||
|         ...ExpectedImage, | ||||
|         message: ExpectedImage.message( | ||||
|           JSON.stringify(options.src), | ||||
|           typeof options.src, | ||||
|           JSON.stringify(options, (_, v) => v === void 0 ? null : v) | ||||
|         ) | ||||
|       }); | ||||
|     } | ||||
|     if (!isESMImportedImage(options.src)) { | ||||
|       if (options.src.startsWith("/@fs/") || !isRemotePath(options.src) && !options.src.startsWith("/")) { | ||||
|         throw new AstroError({ | ||||
|           ...LocalImageUsedWrongly, | ||||
|           message: LocalImageUsedWrongly.message(options.src) | ||||
|         }); | ||||
|       } | ||||
|       let missingDimension; | ||||
|       if (!options.width && !options.height) { | ||||
|         missingDimension = "both"; | ||||
|       } else if (!options.width && options.height) { | ||||
|         missingDimension = "width"; | ||||
|       } else if (options.width && !options.height) { | ||||
|         missingDimension = "height"; | ||||
|       } | ||||
|       if (missingDimension) { | ||||
|         throw new AstroError({ | ||||
|           ...MissingImageDimension, | ||||
|           message: MissingImageDimension.message(missingDimension, options.src) | ||||
|         }); | ||||
|       } | ||||
|     } else { | ||||
|       if (!VALID_SUPPORTED_FORMATS.includes(options.src.format)) { | ||||
|         throw new AstroError({ | ||||
|           ...UnsupportedImageFormat, | ||||
|           message: UnsupportedImageFormat.message( | ||||
|             options.src.format, | ||||
|             options.src.src, | ||||
|             VALID_SUPPORTED_FORMATS | ||||
|           ) | ||||
|         }); | ||||
|       } | ||||
|       if (options.widths && options.densities) { | ||||
|         throw new AstroError(IncompatibleDescriptorOptions); | ||||
|       } | ||||
|       if (options.src.format === "svg") { | ||||
|         options.format = "svg"; | ||||
|       } | ||||
|       if (options.src.format === "svg" && options.format !== "svg" || options.src.format !== "svg" && options.format === "svg") { | ||||
|         throw new AstroError(UnsupportedImageConversion); | ||||
|       } | ||||
|     } | ||||
|     if (!options.format) { | ||||
|       options.format = DEFAULT_OUTPUT_FORMAT; | ||||
|     } | ||||
|     if (options.width) | ||||
|       options.width = Math.round(options.width); | ||||
|     if (options.height) | ||||
|       options.height = Math.round(options.height); | ||||
|     return options; | ||||
|   }, | ||||
|   getHTMLAttributes(options) { | ||||
|     const { targetWidth, targetHeight } = getTargetDimensions(options); | ||||
|     const { src, width, height, format, quality, densities, widths, formats, ...attributes } = options; | ||||
|     return { | ||||
|       ...attributes, | ||||
|       width: targetWidth, | ||||
|       height: targetHeight, | ||||
|       loading: attributes.loading ?? "lazy", | ||||
|       decoding: attributes.decoding ?? "async" | ||||
|     }; | ||||
|   }, | ||||
|   getSrcSet(options) { | ||||
|     const srcSet = []; | ||||
|     const { targetWidth } = getTargetDimensions(options); | ||||
|     const { widths, densities } = options; | ||||
|     const targetFormat = options.format ?? DEFAULT_OUTPUT_FORMAT; | ||||
|     let imageWidth = options.width; | ||||
|     let maxWidth = Infinity; | ||||
|     if (isESMImportedImage(options.src)) { | ||||
|       imageWidth = options.src.width; | ||||
|       maxWidth = imageWidth; | ||||
|     } | ||||
|     const { | ||||
|       width: transformWidth, | ||||
|       height: transformHeight, | ||||
|       ...transformWithoutDimensions | ||||
|     } = options; | ||||
|     const allWidths = []; | ||||
|     if (densities) { | ||||
|       const densityValues = densities.map((density) => { | ||||
|         if (typeof density === "number") { | ||||
|           return density; | ||||
|         } else { | ||||
|           return parseFloat(density); | ||||
|         } | ||||
|       }); | ||||
|       const densityWidths = densityValues.sort().map((density) => Math.round(targetWidth * density)); | ||||
|       allWidths.push( | ||||
|         ...densityWidths.map((width, index) => ({ | ||||
|           maxTargetWidth: Math.min(width, maxWidth), | ||||
|           descriptor: `${densityValues[index]}x` | ||||
|         })) | ||||
|       ); | ||||
|     } else if (widths) { | ||||
|       allWidths.push( | ||||
|         ...widths.map((width) => ({ | ||||
|           maxTargetWidth: Math.min(width, maxWidth), | ||||
|           descriptor: `${width}w` | ||||
|         })) | ||||
|       ); | ||||
|     } | ||||
|     for (const { maxTargetWidth, descriptor } of allWidths) { | ||||
|       const srcSetTransform = { ...transformWithoutDimensions }; | ||||
|       if (maxTargetWidth !== imageWidth) { | ||||
|         srcSetTransform.width = maxTargetWidth; | ||||
|       } else { | ||||
|         if (options.width && options.height) { | ||||
|           srcSetTransform.width = options.width; | ||||
|           srcSetTransform.height = options.height; | ||||
|         } | ||||
|       } | ||||
|       srcSet.push({ | ||||
|         transform: srcSetTransform, | ||||
|         descriptor, | ||||
|         attributes: { | ||||
|           type: `image/${targetFormat}` | ||||
|         } | ||||
|       }); | ||||
|     } | ||||
|     return srcSet; | ||||
|   }, | ||||
|   getURL(options, imageConfig) { | ||||
|     const searchParams = new URLSearchParams(); | ||||
|     if (isESMImportedImage(options.src)) { | ||||
|       searchParams.append("href", options.src.src); | ||||
|     } else if (isRemoteAllowed(options.src, imageConfig)) { | ||||
|       searchParams.append("href", options.src); | ||||
|     } else { | ||||
|       return options.src; | ||||
|     } | ||||
|     const params = { | ||||
|       w: "width", | ||||
|       h: "height", | ||||
|       q: "quality", | ||||
|       f: "format" | ||||
|     }; | ||||
|     Object.entries(params).forEach(([param, key]) => { | ||||
|       options[key] && searchParams.append(param, options[key].toString()); | ||||
|     }); | ||||
|     const imageEndpoint = joinPaths("/", "/_image"); | ||||
|     return `${imageEndpoint}?${searchParams}`; | ||||
|   }, | ||||
|   parseURL(url) { | ||||
|     const params = url.searchParams; | ||||
|     if (!params.has("href")) { | ||||
|       return void 0; | ||||
|     } | ||||
|     const transform = { | ||||
|       src: params.get("href"), | ||||
|       width: params.has("w") ? parseInt(params.get("w")) : void 0, | ||||
|       height: params.has("h") ? parseInt(params.get("h")) : void 0, | ||||
|       format: params.get("f"), | ||||
|       quality: params.get("q") | ||||
|     }; | ||||
|     return transform; | ||||
|   } | ||||
| }; | ||||
| function getTargetDimensions(options) { | ||||
|   let targetWidth = options.width; | ||||
|   let targetHeight = options.height; | ||||
|   if (isESMImportedImage(options.src)) { | ||||
|     const aspectRatio = options.src.width / options.src.height; | ||||
|     if (targetHeight && !targetWidth) { | ||||
|       targetWidth = Math.round(targetHeight * aspectRatio); | ||||
|     } else if (targetWidth && !targetHeight) { | ||||
|       targetHeight = Math.round(targetWidth / aspectRatio); | ||||
|     } else if (!targetWidth && !targetHeight) { | ||||
|       targetWidth = options.src.width; | ||||
|       targetHeight = options.src.height; | ||||
|     } | ||||
|   } | ||||
|   return { | ||||
|     targetWidth, | ||||
|     targetHeight | ||||
|   }; | ||||
| } | ||||
|  | ||||
| let sharp; | ||||
| const qualityTable = { | ||||
|   low: 25, | ||||
|   mid: 50, | ||||
|   high: 80, | ||||
|   max: 100 | ||||
| }; | ||||
| async function loadSharp() { | ||||
|   let sharpImport; | ||||
|   try { | ||||
|     sharpImport = (await import('sharp')).default; | ||||
|   } catch (e) { | ||||
|     throw new AstroError(MissingSharp); | ||||
|   } | ||||
|   return sharpImport; | ||||
| } | ||||
| const sharpService = { | ||||
|   validateOptions: baseService.validateOptions, | ||||
|   getURL: baseService.getURL, | ||||
|   parseURL: baseService.parseURL, | ||||
|   getHTMLAttributes: baseService.getHTMLAttributes, | ||||
|   getSrcSet: baseService.getSrcSet, | ||||
|   async transform(inputBuffer, transformOptions, config) { | ||||
|     if (!sharp) | ||||
|       sharp = await loadSharp(); | ||||
|     const transform = transformOptions; | ||||
|     if (transform.format === "svg") | ||||
|       return { data: inputBuffer, format: "svg" }; | ||||
|     const result = sharp(inputBuffer, { | ||||
|       failOnError: false, | ||||
|       pages: -1, | ||||
|       limitInputPixels: config.service.config.limitInputPixels | ||||
|     }); | ||||
|     result.rotate(); | ||||
|     if (transform.height && !transform.width) { | ||||
|       result.resize({ height: Math.round(transform.height) }); | ||||
|     } else if (transform.width) { | ||||
|       result.resize({ width: Math.round(transform.width) }); | ||||
|     } | ||||
|     if (transform.format) { | ||||
|       let quality = void 0; | ||||
|       if (transform.quality) { | ||||
|         const parsedQuality = parseQuality(transform.quality); | ||||
|         if (typeof parsedQuality === "number") { | ||||
|           quality = parsedQuality; | ||||
|         } else { | ||||
|           quality = transform.quality in qualityTable ? qualityTable[transform.quality] : void 0; | ||||
|         } | ||||
|       } | ||||
|       result.toFormat(transform.format, { quality }); | ||||
|     } | ||||
|     const { data, info } = await result.toBuffer({ resolveWithObject: true }); | ||||
|     return { | ||||
|       data, | ||||
|       format: info.format | ||||
|     }; | ||||
|   } | ||||
| }; | ||||
| var sharp_default = sharpService; | ||||
|  | ||||
| const sharp$1 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({ | ||||
|   __proto__: null, | ||||
|   default: sharp_default | ||||
| }, Symbol.toStringTag, { value: 'Module' })); | ||||
|  | ||||
| export { DEFAULT_HASH_PROPS as D, isLocalService as a, isRemoteImage as b, isRemoteAllowed as c, isESMImportedImage as i, sharp$1 as s }; | ||||
							
								
								
									
										2196
									
								
								test/data/warden/dist/server/chunks/astro_5WdVqH1c.mjs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2196
									
								
								test/data/warden/dist/server/chunks/astro_5WdVqH1c.mjs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										6
									
								
								test/data/warden/dist/server/chunks/index_5_WFUSxR.mjs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								test/data/warden/dist/server/chunks/index_5_WFUSxR.mjs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| export { renderers } from '../renderers.mjs'; | ||||
| export { onRequest } from '../_empty-middleware.mjs'; | ||||
|  | ||||
| const page = () => import('./pages/index_l5vwnKzb.mjs').then(n => n.b); | ||||
|  | ||||
| export { page }; | ||||
							
								
								
									
										6
									
								
								test/data/warden/dist/server/chunks/index_6C3b8yBv.mjs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								test/data/warden/dist/server/chunks/index_6C3b8yBv.mjs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| export { renderers } from '../renderers.mjs'; | ||||
| export { onRequest } from '../_empty-middleware.mjs'; | ||||
|  | ||||
| const page = () => import('./pages/index_l5vwnKzb.mjs').then(n => n.i); | ||||
|  | ||||
| export { page }; | ||||
							
								
								
									
										6
									
								
								test/data/warden/dist/server/chunks/index_Ij1Dwoh1.mjs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								test/data/warden/dist/server/chunks/index_Ij1Dwoh1.mjs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| export { renderers } from '../renderers.mjs'; | ||||
| export { onRequest } from '../_empty-middleware.mjs'; | ||||
|  | ||||
| const page = () => import('./pages/index_l5vwnKzb.mjs').then(n => n.a); | ||||
|  | ||||
| export { page }; | ||||
							
								
								
									
										6
									
								
								test/data/warden/dist/server/chunks/node_0Fr8CwHA.mjs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								test/data/warden/dist/server/chunks/node_0Fr8CwHA.mjs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| export { renderers } from '../renderers.mjs'; | ||||
| export { onRequest } from '../_empty-middleware.mjs'; | ||||
|  | ||||
| const page = () => import('./pages/node_hIg2I-Kh.mjs'); | ||||
|  | ||||
| export { page }; | ||||
							
								
								
									
										245
									
								
								test/data/warden/dist/server/chunks/pages/_slug__TUDhKBhQ.mjs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										245
									
								
								test/data/warden/dist/server/chunks/pages/_slug__TUDhKBhQ.mjs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,245 @@ | ||||
| /* empty css                           */ | ||||
| import 'html-escaper'; | ||||
| import { c as createAstro, d as createComponent, r as renderTemplate, m as maybeRenderHead, e as addAttribute, f as renderSlot, g as renderTransition, h as renderComponent, i as renderHead } from '../astro_5WdVqH1c.mjs'; | ||||
| import 'kleur/colors'; | ||||
| import 'clsx'; | ||||
| import { DocumentRenderer } from '@keystone-6/document-renderer'; | ||||
| /* empty css                           */ | ||||
| /* empty css                           */ | ||||
|  | ||||
| const $$Astro$7 = createAstro("https://smartsheep.studio"); | ||||
| const $$Navbar = createComponent(async ($$result, $$props, $$slots) => { | ||||
|   const Astro2 = $$result.createAstro($$Astro$7, $$props, $$slots); | ||||
|   Astro2.self = $$Navbar; | ||||
|   const items = [ | ||||
|     { | ||||
|       label: "\u60C5\u62A5", | ||||
|       children: [ | ||||
|         { href: "/posts", label: "\u8BB0\u5F55" }, | ||||
|         { href: "/events", label: "\u6D3B\u52A8" } | ||||
|       ] | ||||
|     } | ||||
|   ]; | ||||
|   return renderTemplate`${maybeRenderHead()}<div class="fixed top-0 navbar shadow-md bg-base-100 lg:px-5 z-10"> <div class="navbar-start"> <div class="dropdown"> <div tabindex="0" role="button" class="btn btn-ghost lg:hidden"> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h8m-8 6h16"></path> </svg> </div> <ul tabindex="0" class="menu menu-sm dropdown-content mt-3 z-[1] p-2 shadow bg-base-100 rounded-box w-52"> ${items.map((item) => renderTemplate`<li> <a${addAttribute(item.href, "href")}>${item.label}</a> ${item.children && renderTemplate`<ul class="p-2"> ${item.children?.map((child) => renderTemplate`<li> <a${addAttribute(child.href, "href")}>${child.label}</a> </li>`)} </ul>`} </li>`)} </ul> </div> <a class="btn btn-ghost text-xl" href="/">山羊寒舍</a> </div> <div class="navbar-center hidden lg:flex"> <ul class="menu menu-horizontal px-1"> ${items.map((item) => renderTemplate`<li> ${item.children ? renderTemplate`<details> <summary>${item.label}</summary> <ul class="p-2"> ${item.children?.map((child) => renderTemplate`<li> <a${addAttribute(child.href, "href")}>${child.label}</a> </li>`)} </ul> </details>` : renderTemplate`<a${addAttribute(item.href, "href")}>${item.label}</a>`} </li>`)} </ul> </div> <div class="navbar-end"> <label class="swap swap-rotate px-[16px]"> <input type="checkbox" class="theme-controller" value="light" checked> <svg class="swap-on fill-current w-8 h-8" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path d="M5.64,17l-.71.71a1,1,0,0,0,0,1.41,1,1,0,0,0,1.41,0l.71-.71A1,1,0,0,0,5.64,17ZM5,12a1,1,0,0,0-1-1H3a1,1,0,0,0,0,2H4A1,1,0,0,0,5,12Zm7-7a1,1,0,0,0,1-1V3a1,1,0,0,0-2,0V4A1,1,0,0,0,12,5ZM5.64,7.05a1,1,0,0,0,.7.29,1,1,0,0,0,.71-.29,1,1,0,0,0,0-1.41l-.71-.71A1,1,0,0,0,4.93,6.34Zm12,.29a1,1,0,0,0,.7-.29l.71-.71a1,1,0,1,0-1.41-1.41L17,5.64a1,1,0,0,0,0,1.41A1,1,0,0,0,17.66,7.34ZM21,11H20a1,1,0,0,0,0,2h1a1,1,0,0,0,0-2Zm-9,8a1,1,0,0,0-1,1v1a1,1,0,0,0,2,0V20A1,1,0,0,0,12,19ZM18.36,17A1,1,0,0,0,17,18.36l.71.71a1,1,0,0,0,1.41,0,1,1,0,0,0,0-1.41ZM12,6.5A5.5,5.5,0,1,0,17.5,12,5.51,5.51,0,0,0,12,6.5Zm0,9A3.5,3.5,0,1,1,15.5,12,3.5,3.5,0,0,1,12,15.5Z"></path> </svg> <svg class="swap-off fill-current w-8 h-8" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path d="M21.64,13a1,1,0,0,0-1.05-.14,8.05,8.05,0,0,1-3.37.73A8.15,8.15,0,0,1,9.08,5.49a8.59,8.59,0,0,1,.25-2A1,1,0,0,0,8,2.36,10.14,10.14,0,1,0,22,14.05,1,1,0,0,0,21.64,13Zm-9.5,6.69A8.14,8.14,0,0,1,7.08,5.22v.27A10.15,10.15,0,0,0,17.22,15.63a9.79,9.79,0,0,0,2.1-.22A8.11,8.11,0,0,1,12.14,19.73Z"></path> </svg> </label> </div> </div>`; | ||||
| }, "/Users/littlesheep/Documents/Projects/Capital/src/components/Navbar.astro", void 0); | ||||
|  | ||||
| const $$Astro$6 = createAstro("https://smartsheep.studio"); | ||||
| const $$ViewTransitions = createComponent(async ($$result, $$props, $$slots) => { | ||||
|   const Astro2 = $$result.createAstro($$Astro$6, $$props, $$slots); | ||||
|   Astro2.self = $$ViewTransitions; | ||||
|   const { fallback = "animate" } = Astro2.props; | ||||
|   return renderTemplate`<meta name="astro-view-transitions-enabled" content="true"><meta name="astro-view-transitions-fallback"${addAttribute(fallback, "content")}>`; | ||||
| }, "/Users/littlesheep/Documents/Projects/Capital/node_modules/astro/components/ViewTransitions.astro", void 0); | ||||
|  | ||||
| var __freeze = Object.freeze; | ||||
| var __defProp = Object.defineProperty; | ||||
| var __template = (cooked, raw) => __freeze(__defProp(cooked, "raw", { value: __freeze(raw || cooked.slice()) })); | ||||
| var _a; | ||||
| const $$Astro$5 = createAstro("https://smartsheep.studio"); | ||||
| const $$RootLayout = createComponent(async ($$result, $$props, $$slots) => { | ||||
|   const Astro2 = $$result.createAstro($$Astro$5, $$props, $$slots); | ||||
|   Astro2.self = $$RootLayout; | ||||
|   const { title } = Astro2.props; | ||||
|   return renderTemplate(_a || (_a = __template(['<html lang="en" data-astro-cid-mdysn4oi> <head><meta charset="utf-8"><link rel="icon" type="image/svg+xml" href="/favicon.svg"><meta name="viewport" content="width=device-width"><meta name="generator"', ">", "", "", "", "</head> <body data-astro-cid-mdysn4oi> <!-- Header --> ", " <!-- Content --> <main data-astro-cid-mdysn4oi", "> ", ' </main> <!-- Styles -->   <script async src="https://analytics.smartsheep.studio/script.js" data-website-id="9d676a27-b473-44a3-b444-5a7d851e31e8"><\/script> </body> </html>'])), addAttribute(Astro2.generator, "content"), title && renderTemplate`<title>山羊寒舍 | ${title}</title>`, !title && renderTemplate`<title>山羊寒舍</title>`, renderComponent($$result, "ViewTransitions", $$ViewTransitions, { "data-astro-cid-mdysn4oi": true }), renderHead(), renderComponent($$result, "Navbar", $$Navbar, { "data-astro-cid-mdysn4oi": true }), addAttribute(renderTransition($$result, "53mar5bf", "slide", ""), "data-astro-transition-scope"), renderSlot($$result, $$slots["default"])); | ||||
| }, "/Users/littlesheep/Documents/Projects/Capital/src/layouts/RootLayout.astro", "self"); | ||||
|  | ||||
| const $$Astro$4 = createAstro("https://smartsheep.studio"); | ||||
| const $$PageLayout = createComponent(async ($$result, $$props, $$slots) => { | ||||
|   const Astro2 = $$result.createAstro($$Astro$4, $$props, $$slots); | ||||
|   Astro2.self = $$PageLayout; | ||||
|   const { title } = Astro2.props; | ||||
|   return renderTemplate`${renderComponent($$result, "RootLayout", $$RootLayout, { "title": title }, { "default": ($$result2) => renderTemplate` ${maybeRenderHead()}<main class="container mx-auto h-fullpage mt-header"> ${renderSlot($$result2, $$slots["default"])} </main> ` })}`; | ||||
| }, "/Users/littlesheep/Documents/Projects/Capital/src/layouts/PageLayout.astro", void 0); | ||||
|  | ||||
| const defaultCms = "https://smartsheep.studio"; | ||||
| async function graphQuery(query, variables) { | ||||
|   const response = await fetch(`${process.env.PUBLIC_CMS ?? defaultCms}/api/graphql`, { | ||||
|     method: "POST", | ||||
|     headers: { "Content-Type": "application/json" }, | ||||
|     body: JSON.stringify({ | ||||
|       query, | ||||
|       variables | ||||
|     }) | ||||
|   }); | ||||
|   return await response.json(); | ||||
| } | ||||
|  | ||||
| const POST_TYPES = { | ||||
|   article: "文章", | ||||
|   podcast: "播客", | ||||
|   announcements: "通告" | ||||
| }; | ||||
|  | ||||
| const $$Astro$3 = createAstro("https://smartsheep.studio"); | ||||
| const $$PostList = createComponent(async ($$result, $$props, $$slots) => { | ||||
|   const Astro2 = $$result.createAstro($$Astro$3, $$props, $$slots); | ||||
|   Astro2.self = $$PostList; | ||||
|   const { posts } = Astro2.props; | ||||
|   return renderTemplate`${maybeRenderHead()}<div class="grid justify-items-strench shadow-lg"> ${posts?.map((item) => renderTemplate`<a${addAttribute(`/p/${item.slug}`, "href")}> <div class="card sm:card-side hover:bg-base-200 transition-colors sm:max-w-none"> ${item.cover.image.url && renderTemplate`<figure class="mx-auto w-full object-cover p-6 max-sm:pb-0 sm:max-w-[12rem] sm:pe-0"> <img loading="lazy"${addAttribute(item.cover.image.url, "src")} class="border-base-content bg-base-300 rounded-btn border border-opacity-5"${addAttribute(item.title, "alt")}> </figure>`} <div class="card-body"> <h2 class="text-xl">${item.title}</h2> <div class="mx-[-2px] mt-[-4px]"> <span class="badge badge-accent">${POST_TYPES[item.type]}</span> ${item.categories?.map((category) => renderTemplate`<span class="badge badge-primary">${category.name}</span>`)} ${item.tags?.map((tag) => renderTemplate`<span class="badge badge-secondary">${tag.name}</span>`)} </div> <div class="text-xs opacity-60 line-clamp-3"> ${item.description} </div> </div> </div> </a>`)} </div>`; | ||||
| }, "/Users/littlesheep/Documents/Projects/Capital/src/components/PostList.astro", void 0); | ||||
|  | ||||
| const $$Astro$2 = createAstro("https://smartsheep.studio"); | ||||
| const prerender$2 = false; | ||||
| const $$slug$2 = createComponent(async ($$result, $$props, $$slots) => { | ||||
|   const Astro2 = $$result.createAstro($$Astro$2, $$props, $$slots); | ||||
|   Astro2.self = $$slug$2; | ||||
|   const { slug } = Astro2.params; | ||||
|   const { posts } = (await graphQuery( | ||||
|     `query Query($where: PostWhereInput!, $orderBy: [PostOrderByInput!]!) { | ||||
|   posts(where: $where, orderBy: $orderBy) { | ||||
|     slug | ||||
|     type | ||||
|     title | ||||
|     description | ||||
|     cover { | ||||
|       image { | ||||
|         url | ||||
|       } | ||||
|     } | ||||
|     content { | ||||
|       document | ||||
|     } | ||||
|     categories { | ||||
|       name | ||||
|     } | ||||
|     tags { | ||||
|       name | ||||
|     } | ||||
|     createdAt | ||||
|   } | ||||
| }`, | ||||
|     { | ||||
|       orderBy: [ | ||||
|         { | ||||
|           createdAt: "desc" | ||||
|         } | ||||
|       ], | ||||
|       where: { categories: { some: { slug: { equals: slug } } } } | ||||
|     } | ||||
|   )).data; | ||||
|   return renderTemplate`${renderComponent($$result, "PageLayout", $$PageLayout, { "title": "\u5206\u7C7B\u68C0\u7D22" }, { "default": ($$result2) => renderTemplate` ${maybeRenderHead()}<div class="max-w-[720px] mx-auto"> <div class="pt-16 pb-6 px-6"> <h1 class="text-4xl font-bold">分类检索</h1> <p class="pt-3">以下是包含该分类的记录……</p> </div> ${renderComponent($$result2, "PostList", $$PostList, { "posts": posts })} </div> ` })}`; | ||||
| }, "/Users/littlesheep/Documents/Projects/Capital/src/pages/categories/[slug].astro", void 0); | ||||
|  | ||||
| const $$file$2 = "/Users/littlesheep/Documents/Projects/Capital/src/pages/categories/[slug].astro"; | ||||
| const $$url$2 = "/categories/[slug]"; | ||||
|  | ||||
| const _slug_$2 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({ | ||||
|   __proto__: null, | ||||
|   default: $$slug$2, | ||||
|   file: $$file$2, | ||||
|   prerender: prerender$2, | ||||
|   url: $$url$2 | ||||
| }, Symbol.toStringTag, { value: 'Module' })); | ||||
|  | ||||
| const $$Astro$1 = createAstro("https://smartsheep.studio"); | ||||
| const prerender$1 = false; | ||||
| const $$slug$1 = createComponent(async ($$result, $$props, $$slots) => { | ||||
|   const Astro2 = $$result.createAstro($$Astro$1, $$props, $$slots); | ||||
|   Astro2.self = $$slug$1; | ||||
|   const { slug } = Astro2.params; | ||||
|   const { post } = (await graphQuery( | ||||
|     `query Query($where: PostWhereUniqueInput!) { | ||||
|   post(where: $where) { | ||||
|     slug | ||||
|     type | ||||
|     title | ||||
|     description | ||||
|     author { | ||||
|       name | ||||
|     } | ||||
|     assets { | ||||
|       caption | ||||
|       url | ||||
|       type | ||||
|     } | ||||
|     cover { | ||||
|       image { | ||||
|         url | ||||
|       } | ||||
|     } | ||||
|     content { | ||||
|       document | ||||
|     } | ||||
|     categories { | ||||
|       slug | ||||
|       name | ||||
|     } | ||||
|     tags { | ||||
|       slug | ||||
|       name | ||||
|     } | ||||
|     createdAt | ||||
|   } | ||||
| }`, | ||||
|     { | ||||
|       where: { slug } | ||||
|     } | ||||
|   )).data; | ||||
|   return renderTemplate`${renderComponent($$result, "PageLayout", $$PageLayout, { "title": post.title, "data-astro-cid-gysqo7gh": true }, { "default": ($$result2) => renderTemplate` ${maybeRenderHead()}<div class="wrapper" data-astro-cid-gysqo7gh> <div class="card w-full shadow-xl" data-astro-cid-gysqo7gh> ${post.cover && renderTemplate`<figure data-astro-cid-gysqo7gh> <img${addAttribute(post.cover.image.url, "src")}${addAttribute(post.title, "alt")} data-astro-cid-gysqo7gh> </figure>`} <div class="card-body" data-astro-cid-gysqo7gh> <h2 class="card-title" data-astro-cid-gysqo7gh>${post.title}</h2> <p class="description" data-astro-cid-gysqo7gh>${post.description ?? "No description"}</p> <div class="divider" data-astro-cid-gysqo7gh></div> ${post.assets?.length > 0 && renderTemplate`<div class="mb-5 w-full" data-astro-cid-gysqo7gh> ${renderComponent($$result2, "Media", null, { "client:only": true, "sources": post.assets, "author": post.author, "client:component-hydration": "only", "data-astro-cid-gysqo7gh": true, "client:component-path": "/Users/littlesheep/Documents/Projects/Capital/src/components/posts/Media", "client:component-export": "default" })} </div>`} <div class="prose max-w-none" data-astro-cid-gysqo7gh> ${renderComponent($$result2, "DocumentRenderer", DocumentRenderer, { "document": post.content.document, "data-astro-cid-gysqo7gh": true })} </div> </div> </div> <div class="h-fit sticky top-header" data-astro-cid-gysqo7gh> <div class="card shadow-xl" data-astro-cid-gysqo7gh> <div class="card-body" data-astro-cid-gysqo7gh> <div class="gap-2 text-sm metadata description" data-astro-cid-gysqo7gh> <div data-astro-cid-gysqo7gh> <div data-astro-cid-gysqo7gh>作者</div> <div data-astro-cid-gysqo7gh>${post.author?.name ?? "\u4F5A\u540D"}</div> </div> <div data-astro-cid-gysqo7gh> <div data-astro-cid-gysqo7gh>类型</div> <div class="text-accent" data-astro-cid-gysqo7gh> ${POST_TYPES[post.type]} </div> </div> <div data-astro-cid-gysqo7gh> <div data-astro-cid-gysqo7gh>分类</div> <div class="flex gap-1" data-astro-cid-gysqo7gh> ${post.categories?.map((category) => renderTemplate`<a${addAttribute(`/categories/${category.slug}`, "href")} class="link link-primary" data-astro-cid-gysqo7gh> ${category.name} </a>`)} </div> </div> <div data-astro-cid-gysqo7gh> <div data-astro-cid-gysqo7gh>标签</div> <div class="flex gap-1" data-astro-cid-gysqo7gh> ${post.tags?.map((tag) => renderTemplate`<a${addAttribute(`/tags/${tag.slug}`, "href")} class="link link-secondary" data-astro-cid-gysqo7gh> ${tag.name} </a>`)} </div> </div> <div data-astro-cid-gysqo7gh> <div data-astro-cid-gysqo7gh>发布于</div> <div data-astro-cid-gysqo7gh>${new Date(post.createdAt).toLocaleString()}</div> </div> </div> </div> </div> </div> </div> ` })} `; | ||||
| }, "/Users/littlesheep/Documents/Projects/Capital/src/pages/posts/[slug].astro", void 0); | ||||
|  | ||||
| const $$file$1 = "/Users/littlesheep/Documents/Projects/Capital/src/pages/posts/[slug].astro"; | ||||
| const $$url$1 = "/posts/[slug]"; | ||||
|  | ||||
| const _slug_$1 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({ | ||||
|   __proto__: null, | ||||
|   default: $$slug$1, | ||||
|   file: $$file$1, | ||||
|   prerender: prerender$1, | ||||
|   url: $$url$1 | ||||
| }, Symbol.toStringTag, { value: 'Module' })); | ||||
|  | ||||
| const $$Astro = createAstro("https://smartsheep.studio"); | ||||
| const prerender = false; | ||||
| const $$slug = createComponent(async ($$result, $$props, $$slots) => { | ||||
|   const Astro2 = $$result.createAstro($$Astro, $$props, $$slots); | ||||
|   Astro2.self = $$slug; | ||||
|   const { slug } = Astro2.params; | ||||
|   const { posts } = (await graphQuery( | ||||
|     `query Query($where: PostWhereInput!, $orderBy: [PostOrderByInput!]!) { | ||||
|   posts(where: $where, orderBy: $orderBy) { | ||||
|     slug | ||||
|     type | ||||
|     title | ||||
|     description | ||||
|     cover { | ||||
|       image { | ||||
|         url | ||||
|       } | ||||
|     } | ||||
|     content { | ||||
|       document | ||||
|     } | ||||
|     categories { | ||||
|       name | ||||
|     } | ||||
|     tags { | ||||
|       name | ||||
|     } | ||||
|     createdAt | ||||
|   } | ||||
| }`, | ||||
|     { | ||||
|       orderBy: [ | ||||
|         { | ||||
|           createdAt: "desc" | ||||
|         } | ||||
|       ], | ||||
|       where: { tags: { some: { slug: { equals: slug } } } } | ||||
|     } | ||||
|   )).data; | ||||
|   return renderTemplate`${renderComponent($$result, "PageLayout", $$PageLayout, { "title": "\u6807\u7B7E\u68C0\u7D22" }, { "default": ($$result2) => renderTemplate` ${maybeRenderHead()}<div class="max-w-[720px] mx-auto"> <div class="pt-16 pb-6 px-6"> <h1 class="text-4xl font-bold">标签检索</h1> <p class="pt-3">以下是包含该标签的记录……</p> </div> ${renderComponent($$result2, "PostList", $$PostList, { "posts": posts })} </div> ` })}`; | ||||
| }, "/Users/littlesheep/Documents/Projects/Capital/src/pages/tags/[slug].astro", void 0); | ||||
|  | ||||
| const $$file = "/Users/littlesheep/Documents/Projects/Capital/src/pages/tags/[slug].astro"; | ||||
| const $$url = "/tags/[slug]"; | ||||
|  | ||||
| const _slug_ = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({ | ||||
|   __proto__: null, | ||||
|   default: $$slug, | ||||
|   file: $$file, | ||||
|   prerender, | ||||
|   url: $$url | ||||
| }, Symbol.toStringTag, { value: 'Module' })); | ||||
|  | ||||
| export { $$PageLayout as $, _slug_$2 as _, $$PostList as a, $$RootLayout as b, _slug_$1 as c, _slug_ as d, graphQuery as g }; | ||||
							
								
								
									
										153
									
								
								test/data/warden/dist/server/chunks/pages/index_l5vwnKzb.mjs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								test/data/warden/dist/server/chunks/pages/index_l5vwnKzb.mjs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,153 @@ | ||||
| /* empty css                           */ | ||||
| import { c as createAstro, d as createComponent, r as renderTemplate, h as renderComponent, m as maybeRenderHead, e as addAttribute } from '../astro_5WdVqH1c.mjs'; | ||||
| import 'kleur/colors'; | ||||
| import 'html-escaper'; | ||||
| import { g as graphQuery, $ as $$PageLayout, a as $$PostList, b as $$RootLayout } from './_slug__TUDhKBhQ.mjs'; | ||||
| import { DocumentRenderer } from '@keystone-6/document-renderer'; | ||||
| import 'clsx'; | ||||
| /* empty css                          */ | ||||
|  | ||||
| const $$Astro$2 = createAstro("https://smartsheep.studio"); | ||||
| const prerender$2 = false; | ||||
| const $$Index$2 = createComponent(async ($$result, $$props, $$slots) => { | ||||
|   const Astro2 = $$result.createAstro($$Astro$2, $$props, $$slots); | ||||
|   Astro2.self = $$Index$2; | ||||
|   const { events } = (await graphQuery( | ||||
|     `query Query($where: EventWhereInput!) { | ||||
|   events(where: $where) { | ||||
|     slug | ||||
|     title | ||||
|     description | ||||
|     content { | ||||
|       document | ||||
|     } | ||||
|     createdAt | ||||
|   } | ||||
| }`, | ||||
|     { | ||||
|       where: { | ||||
|         isHistory: { | ||||
|           equals: true | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   )).data; | ||||
|   return renderTemplate`${renderComponent($$result, "PageLayout", $$PageLayout, { "title": "\u6D3B\u52A8" }, { "default": ($$result2) => renderTemplate` ${maybeRenderHead()}<div class="max-w-[720px] mx-auto"> <div class="card w-full shadow-xl"> <div class="card-body"> <h2 class="card-title">活动</h2> <p>读岁月史书,涨人生阅历</p> <div class="divider"></div> <ul class="timeline timeline-snap-icon max-md:timeline-compact timeline-vertical"> ${events?.map((item, idx) => { | ||||
|     let align = idx % 2 === 0 ? "timeline-start" : "timeline-end"; | ||||
|     let textAlign = idx % 2 === 0 ? "md:text-right" : "md:text-left"; | ||||
|     return renderTemplate`<li> ${idx > 0 && renderTemplate`<hr>`} <div class="timeline-middle"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-5 w-5"> <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd"></path> </svg> </div> <div${addAttribute(`${align} ${textAlign} mb-10`, "class")}> <time class="font-mono italic"> ${new Date(item.createdAt).toLocaleDateString()} </time> <div class="text-lg font-black">${item.title}</div> ${renderComponent($$result2, "DocumentRenderer", DocumentRenderer, { "document": item.content.document })} </div> <hr> </li>`; | ||||
|   })} </ul> <div class="text-center max-md:text-left italic"> | ||||
| 我们的故事还在继续…… | ||||
| </div> </div> </div> </div> ` })}`; | ||||
| }, "/Users/littlesheep/Documents/Projects/Capital/src/pages/events/index.astro", void 0); | ||||
|  | ||||
| const $$file$2 = "/Users/littlesheep/Documents/Projects/Capital/src/pages/events/index.astro"; | ||||
| const $$url$2 = "/events"; | ||||
|  | ||||
| const index$2 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({ | ||||
|   __proto__: null, | ||||
|   default: $$Index$2, | ||||
|   file: $$file$2, | ||||
|   prerender: prerender$2, | ||||
|   url: $$url$2 | ||||
| }, Symbol.toStringTag, { value: 'Module' })); | ||||
|  | ||||
| const $$Astro$1 = createAstro("https://smartsheep.studio"); | ||||
| const prerender$1 = false; | ||||
| const $$Index$1 = createComponent(async ($$result, $$props, $$slots) => { | ||||
|   const Astro2 = $$result.createAstro($$Astro$1, $$props, $$slots); | ||||
|   Astro2.self = $$Index$1; | ||||
|   const { posts } = (await graphQuery( | ||||
|     `query Query($where: PostWhereInput!, $orderBy: [PostOrderByInput!]!) { | ||||
|   posts(where: $where, orderBy: $orderBy) { | ||||
|     slug | ||||
|     type | ||||
|     title | ||||
|     description | ||||
|     cover { | ||||
|       image { | ||||
|         url | ||||
|       } | ||||
|     } | ||||
|     content { | ||||
|       document | ||||
|     } | ||||
|     categories { | ||||
|       name | ||||
|     } | ||||
|     tags { | ||||
|       name | ||||
|     } | ||||
|     createdAt | ||||
|   } | ||||
| }`, | ||||
|     { | ||||
|       orderBy: [ | ||||
|         { | ||||
|           createdAt: "desc" | ||||
|         } | ||||
|       ], | ||||
|       where: {} | ||||
|     } | ||||
|   )).data; | ||||
|   return renderTemplate`${renderComponent($$result, "PageLayout", $$PageLayout, { "title": "\u8BB0\u5F55" }, { "default": ($$result2) => renderTemplate` ${maybeRenderHead()}<div class="max-w-[720px] mx-auto"> <div class="pt-16 pb-6 px-6"> <h1 class="text-4xl font-bold">记录</h1> <p class="pt-2">记录生活,记录理想,记录记录……</p> </div> ${renderComponent($$result2, "PostList", $$PostList, { "posts": posts })} </div> ` })}`; | ||||
| }, "/Users/littlesheep/Documents/Projects/Capital/src/pages/posts/index.astro", void 0); | ||||
|  | ||||
| const $$file$1 = "/Users/littlesheep/Documents/Projects/Capital/src/pages/posts/index.astro"; | ||||
| const $$url$1 = "/posts"; | ||||
|  | ||||
| const index$1 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({ | ||||
|   __proto__: null, | ||||
|   default: $$Index$1, | ||||
|   file: $$file$1, | ||||
|   prerender: prerender$1, | ||||
|   url: $$url$1 | ||||
| }, Symbol.toStringTag, { value: 'Module' })); | ||||
|  | ||||
| const $$Astro = createAstro("https://smartsheep.studio"); | ||||
| const prerender = false; | ||||
| const $$Index = createComponent(async ($$result, $$props, $$slots) => { | ||||
|   const Astro2 = $$result.createAstro($$Astro, $$props, $$slots); | ||||
|   Astro2.self = $$Index; | ||||
|   const { events } = (await graphQuery( | ||||
|     `query Query($where: EventWhereInput!) { | ||||
|   events(where: $where) { | ||||
|     slug | ||||
|     title | ||||
|     description | ||||
|     createdAt | ||||
|   } | ||||
| }`, | ||||
|     { | ||||
|       where: { | ||||
|         isHistory: { | ||||
|           equals: true | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   )).data; | ||||
|   return renderTemplate`${renderComponent($$result, "RootLayout", $$RootLayout, { "data-astro-cid-j7pv25f6": true }, { "default": ($$result2) => renderTemplate` ${maybeRenderHead()}<div class="max-h-fullpage mt-header wrapper px-5 snap-y snap-mandatory" data-astro-cid-j7pv25f6> <div id="hello" class="hero h-fullpage snap-start" data-astro-cid-j7pv25f6> <div class="hero-content w-full grid grid-cols-1 md:grid-cols-2 max-md:gap-[60px]" data-astro-cid-j7pv25f6> <div class="max-md:text-center" data-astro-cid-j7pv25f6> <h1 class="text-5xl font-bold" data-astro-cid-j7pv25f6>你好呀 👋</h1> <p class="py-6" data-astro-cid-j7pv25f6> | ||||
| 欢迎来到 SmartSheep Studio | ||||
|             的官方网站!在这里了解,订阅,跟踪我们的最新消息。 | ||||
|             接触我们最大的官方社区,并且尝试最新产品,参与各种活动,提供反馈,让我们更好的服务您。 | ||||
| </p> <a href="#about" class="btn btn-primary btn-md" data-astro-cid-j7pv25f6>了解更多</a> </div> <div class="flex justify-center md:justify-end max-md:order-first" data-astro-cid-j7pv25f6> <div class="spinning p-3 md:p-5 shadow-2xl aspect-square rounded-[30%] w-[192px] md:w-[256px] lg:w-[384px]" data-astro-cid-j7pv25f6> <img src="/favicon.svg" alt="logo" loading="lazy" data-astro-cid-j7pv25f6> </div> </div> </div> </div> <div id="about" class="hero h-fullpage snap-start" data-astro-cid-j7pv25f6> <div class="hero-content w-full grid grid-cols-1 md:grid-cols-2 max-md:gap-[60px]" data-astro-cid-j7pv25f6> <div class="flex justify-center md:justify-start" data-astro-cid-j7pv25f6> <div class="stats shadow overflow-x-auto" data-astro-cid-j7pv25f6> <div class="stat" data-astro-cid-j7pv25f6> <div class="stat-figure text-secondary" data-astro-cid-j7pv25f6> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="inline-block w-8 h-8 stroke-current" data-astro-cid-j7pv25f6><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" data-astro-cid-j7pv25f6></path></svg> </div> <div class="stat-title" data-astro-cid-j7pv25f6>People</div> <div class="stat-value" data-astro-cid-j7pv25f6>1</div> <div class="stat-desc" data-astro-cid-j7pv25f6>2019 - ${(/* @__PURE__ */ new Date()).getFullYear()}</div> </div> <div class="stat" data-astro-cid-j7pv25f6> <div class="stat-figure text-secondary" data-astro-cid-j7pv25f6> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="inline-block w-8 h-8 stroke-current" data-astro-cid-j7pv25f6><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4" data-astro-cid-j7pv25f6></path></svg> </div> <div class="stat-title" data-astro-cid-j7pv25f6>Clients</div> <div class="stat-value" data-astro-cid-j7pv25f6>180</div> <div class="stat-desc" data-astro-cid-j7pv25f6>↗︎ 80 (44%)</div> </div> <div class="stat" data-astro-cid-j7pv25f6> <div class="stat-figure text-secondary" data-astro-cid-j7pv25f6> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="inline-block w-8 h-8 stroke-current" data-astro-cid-j7pv25f6><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4" data-astro-cid-j7pv25f6></path></svg> </div> <div class="stat-title" data-astro-cid-j7pv25f6>Products</div> <div class="stat-value" data-astro-cid-j7pv25f6>4</div> <div class="stat-desc" data-astro-cid-j7pv25f6>↘︎ 8 (67%)</div> </div> </div> </div> <div class="max-md:text-center" data-astro-cid-j7pv25f6> <h1 class="text-5xl font-bold" data-astro-cid-j7pv25f6>关于我们 🔖</h1> <p class="py-6" data-astro-cid-j7pv25f6> | ||||
| 我们是一群充满活力、对开源充满热情的开发者。成立于 2019 | ||||
|             年。自那年起我们一直在开发让人喜欢的开源软件。在我们这里,“取之于开源,用之于开源” | ||||
|             不仅是原则,更是我们信仰的座右铭。 | ||||
| </p> <a href="#history" class="btn btn-primary btn-md pl-[24px]" data-astro-cid-j7pv25f6> | ||||
| 查看「岁月史书」 | ||||
| </a> </div> </div> </div> <div id="history" class="flex flex-col justify-center items-center h-fullpage snap-start" data-astro-cid-j7pv25f6> <div class="text-center" data-astro-cid-j7pv25f6> <div data-astro-cid-j7pv25f6> <h1 class="text-4xl font-bold" data-astro-cid-j7pv25f6>岁月史书</h1> <p class="pt-2 pb-4 tracking-[8px]" data-astro-cid-j7pv25f6>但当涉猎,见往事耳</p> <ul class="pb-6 mx-[-20px] max-w-[100vw] px-5 flex justify-center history timeline timeline-horizontal" data-astro-cid-j7pv25f6> ${events?.map((item, idx) => renderTemplate`<li data-astro-cid-j7pv25f6> ${idx > 0 && renderTemplate`<hr data-astro-cid-j7pv25f6>`} <div class="timeline-start" data-astro-cid-j7pv25f6> ${new Date(item.createdAt).toLocaleDateString()} </div> <div class="timeline-middle" data-astro-cid-j7pv25f6> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5" data-astro-cid-j7pv25f6> <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" data-astro-cid-j7pv25f6></path> </svg> </div> <div class="timeline-end timeline-box" data-astro-cid-j7pv25f6> <h2 class="font-bold text-lg" data-astro-cid-j7pv25f6>${item.title}</h2> <div class="line-clamp-2" data-astro-cid-j7pv25f6>${item.description}</div> </div> ${idx < events?.length - 1 && renderTemplate`<hr data-astro-cid-j7pv25f6>`} </li>`)} </ul> <a class="btn btn-primary" href="/events" data-astro-cid-j7pv25f6>查看更多</a> </div> </div> </div> </div> ` })}  `; | ||||
| }, "/Users/littlesheep/Documents/Projects/Capital/src/pages/index.astro", void 0); | ||||
|  | ||||
| const $$file = "/Users/littlesheep/Documents/Projects/Capital/src/pages/index.astro"; | ||||
| const $$url = ""; | ||||
|  | ||||
| const index = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({ | ||||
|   __proto__: null, | ||||
|   default: $$Index, | ||||
|   file: $$file, | ||||
|   prerender, | ||||
|   url: $$url | ||||
| }, Symbol.toStringTag, { value: 'Module' })); | ||||
|  | ||||
| export { index$1 as a, index as b, index$2 as i }; | ||||
							
								
								
									
										240
									
								
								test/data/warden/dist/server/chunks/pages/node_hIg2I-Kh.mjs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										240
									
								
								test/data/warden/dist/server/chunks/pages/node_hIg2I-Kh.mjs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,240 @@ | ||||
| import { isRemotePath } from '@astrojs/internal-helpers/path'; | ||||
| import { readFile } from 'fs/promises'; | ||||
| import mime from 'mime/lite.js'; | ||||
| import 'os'; | ||||
| import { A as AstroError, j as InvalidImageService, k as ExpectedImageOptions, E as ExpectedImage, c as createAstro, d as createComponent, l as ImageMissingAlt, r as renderTemplate, m as maybeRenderHead, e as addAttribute, s as spreadAttributes } from '../astro_5WdVqH1c.mjs'; | ||||
| import { i as isESMImportedImage, a as isLocalService, b as isRemoteImage, D as DEFAULT_HASH_PROPS, c as isRemoteAllowed } from '../astro/assets-service_4dMyVCFm.mjs'; | ||||
| import 'html-escaper'; | ||||
| import 'clsx'; | ||||
|  | ||||
| async function getConfiguredImageService() { | ||||
|   if (!globalThis?.astroAsset?.imageService) { | ||||
|     const { default: service } = await import( | ||||
|       // @ts-expect-error | ||||
|       '../astro/assets-service_4dMyVCFm.mjs' | ||||
|     ).then(n => n.s).catch((e) => { | ||||
|       const error = new AstroError(InvalidImageService); | ||||
|       error.cause = e; | ||||
|       throw error; | ||||
|     }); | ||||
|     if (!globalThis.astroAsset) | ||||
|       globalThis.astroAsset = {}; | ||||
|     globalThis.astroAsset.imageService = service; | ||||
|     return service; | ||||
|   } | ||||
|   return globalThis.astroAsset.imageService; | ||||
| } | ||||
| async function getImage$1(options, imageConfig) { | ||||
|   if (!options || typeof options !== "object") { | ||||
|     throw new AstroError({ | ||||
|       ...ExpectedImageOptions, | ||||
|       message: ExpectedImageOptions.message(JSON.stringify(options)) | ||||
|     }); | ||||
|   } | ||||
|   if (typeof options.src === "undefined") { | ||||
|     throw new AstroError({ | ||||
|       ...ExpectedImage, | ||||
|       message: ExpectedImage.message( | ||||
|         options.src, | ||||
|         "undefined", | ||||
|         JSON.stringify(options) | ||||
|       ) | ||||
|     }); | ||||
|   } | ||||
|   const service = await getConfiguredImageService(); | ||||
|   const resolvedOptions = { | ||||
|     ...options, | ||||
|     src: typeof options.src === "object" && "then" in options.src ? (await options.src).default ?? await options.src : options.src | ||||
|   }; | ||||
|   const originalPath = isESMImportedImage(resolvedOptions.src) ? resolvedOptions.src.fsPath : resolvedOptions.src; | ||||
|   const clonedSrc = isESMImportedImage(resolvedOptions.src) ? ( | ||||
|     // @ts-expect-error - clone is a private, hidden prop | ||||
|     resolvedOptions.src.clone ?? resolvedOptions.src | ||||
|   ) : resolvedOptions.src; | ||||
|   resolvedOptions.src = clonedSrc; | ||||
|   const validatedOptions = service.validateOptions ? await service.validateOptions(resolvedOptions, imageConfig) : resolvedOptions; | ||||
|   const srcSetTransforms = service.getSrcSet ? await service.getSrcSet(validatedOptions, imageConfig) : []; | ||||
|   let imageURL = await service.getURL(validatedOptions, imageConfig); | ||||
|   let srcSets = await Promise.all( | ||||
|     srcSetTransforms.map(async (srcSet) => ({ | ||||
|       transform: srcSet.transform, | ||||
|       url: await service.getURL(srcSet.transform, imageConfig), | ||||
|       descriptor: srcSet.descriptor, | ||||
|       attributes: srcSet.attributes | ||||
|     })) | ||||
|   ); | ||||
|   if (isLocalService(service) && globalThis.astroAsset.addStaticImage && !(isRemoteImage(validatedOptions.src) && imageURL === validatedOptions.src)) { | ||||
|     const propsToHash = service.propertiesToHash ?? DEFAULT_HASH_PROPS; | ||||
|     imageURL = globalThis.astroAsset.addStaticImage(validatedOptions, propsToHash, originalPath); | ||||
|     srcSets = srcSetTransforms.map((srcSet) => ({ | ||||
|       transform: srcSet.transform, | ||||
|       url: globalThis.astroAsset.addStaticImage(srcSet.transform, propsToHash, originalPath), | ||||
|       descriptor: srcSet.descriptor, | ||||
|       attributes: srcSet.attributes | ||||
|     })); | ||||
|   } | ||||
|   return { | ||||
|     rawOptions: resolvedOptions, | ||||
|     options: validatedOptions, | ||||
|     src: imageURL, | ||||
|     srcSet: { | ||||
|       values: srcSets, | ||||
|       attribute: srcSets.map((srcSet) => `${srcSet.url} ${srcSet.descriptor}`).join(", ") | ||||
|     }, | ||||
|     attributes: service.getHTMLAttributes !== void 0 ? await service.getHTMLAttributes(validatedOptions, imageConfig) : {} | ||||
|   }; | ||||
| } | ||||
|  | ||||
| const fnv1a52 = (str) => { | ||||
|   const len = str.length; | ||||
|   let i = 0, t0 = 0, v0 = 8997, t1 = 0, v1 = 33826, t2 = 0, v2 = 40164, t3 = 0, v3 = 52210; | ||||
|   while (i < len) { | ||||
|     v0 ^= str.charCodeAt(i++); | ||||
|     t0 = v0 * 435; | ||||
|     t1 = v1 * 435; | ||||
|     t2 = v2 * 435; | ||||
|     t3 = v3 * 435; | ||||
|     t2 += v0 << 8; | ||||
|     t3 += v1 << 8; | ||||
|     t1 += t0 >>> 16; | ||||
|     v0 = t0 & 65535; | ||||
|     t2 += t1 >>> 16; | ||||
|     v1 = t1 & 65535; | ||||
|     v3 = t3 + (t2 >>> 16) & 65535; | ||||
|     v2 = t2 & 65535; | ||||
|   } | ||||
|   return (v3 & 15) * 281474976710656 + v2 * 4294967296 + v1 * 65536 + (v0 ^ v3 >> 4); | ||||
| }; | ||||
| const etag = (payload, weak = false) => { | ||||
|   const prefix = weak ? 'W/"' : '"'; | ||||
|   return prefix + fnv1a52(payload).toString(36) + payload.length.toString(36) + '"'; | ||||
| }; | ||||
|  | ||||
| const $$Astro$1 = createAstro("https://smartsheep.studio"); | ||||
| const $$Image = createComponent(async ($$result, $$props, $$slots) => { | ||||
|   const Astro2 = $$result.createAstro($$Astro$1, $$props, $$slots); | ||||
|   Astro2.self = $$Image; | ||||
|   const props = Astro2.props; | ||||
|   if (props.alt === void 0 || props.alt === null) { | ||||
|     throw new AstroError(ImageMissingAlt); | ||||
|   } | ||||
|   if (typeof props.width === "string") { | ||||
|     props.width = parseInt(props.width); | ||||
|   } | ||||
|   if (typeof props.height === "string") { | ||||
|     props.height = parseInt(props.height); | ||||
|   } | ||||
|   const image = await getImage(props); | ||||
|   const additionalAttributes = {}; | ||||
|   if (image.srcSet.values.length > 0) { | ||||
|     additionalAttributes.srcset = image.srcSet.attribute; | ||||
|   } | ||||
|   return renderTemplate`${maybeRenderHead()}<img${addAttribute(image.src, "src")}${spreadAttributes(additionalAttributes)}${spreadAttributes(image.attributes)}>`; | ||||
| }, "/Users/littlesheep/Documents/Projects/Capital/node_modules/astro/components/Image.astro", void 0); | ||||
|  | ||||
| const $$Astro = createAstro("https://smartsheep.studio"); | ||||
| const $$Picture = createComponent(async ($$result, $$props, $$slots) => { | ||||
|   const Astro2 = $$result.createAstro($$Astro, $$props, $$slots); | ||||
|   Astro2.self = $$Picture; | ||||
|   const defaultFormats = ["webp"]; | ||||
|   const defaultFallbackFormat = "png"; | ||||
|   const specialFormatsFallback = ["gif", "svg", "jpg", "jpeg"]; | ||||
|   const { formats = defaultFormats, pictureAttributes = {}, fallbackFormat, ...props } = Astro2.props; | ||||
|   if (props.alt === void 0 || props.alt === null) { | ||||
|     throw new AstroError(ImageMissingAlt); | ||||
|   } | ||||
|   const optimizedImages = await Promise.all( | ||||
|     formats.map( | ||||
|       async (format) => await getImage({ ...props, format, widths: props.widths, densities: props.densities }) | ||||
|     ) | ||||
|   ); | ||||
|   let resultFallbackFormat = fallbackFormat ?? defaultFallbackFormat; | ||||
|   if (!fallbackFormat && isESMImportedImage(props.src) && specialFormatsFallback.includes(props.src.format)) { | ||||
|     resultFallbackFormat = props.src.format; | ||||
|   } | ||||
|   const fallbackImage = await getImage({ | ||||
|     ...props, | ||||
|     format: resultFallbackFormat, | ||||
|     widths: props.widths, | ||||
|     densities: props.densities | ||||
|   }); | ||||
|   const imgAdditionalAttributes = {}; | ||||
|   const sourceAdditionaAttributes = {}; | ||||
|   if (props.sizes) { | ||||
|     sourceAdditionaAttributes.sizes = props.sizes; | ||||
|   } | ||||
|   if (fallbackImage.srcSet.values.length > 0) { | ||||
|     imgAdditionalAttributes.srcset = fallbackImage.srcSet.attribute; | ||||
|   } | ||||
|   return renderTemplate`${maybeRenderHead()}<picture${spreadAttributes(pictureAttributes)}> ${Object.entries(optimizedImages).map(([_, image]) => { | ||||
|     const srcsetAttribute = props.densities || !props.densities && !props.widths ? `${image.src}${image.srcSet.values.length > 0 ? ", " + image.srcSet.attribute : ""}` : image.srcSet.attribute; | ||||
|     return renderTemplate`<source${addAttribute(srcsetAttribute, "srcset")}${addAttribute("image/" + image.options.format, "type")}${spreadAttributes(sourceAdditionaAttributes)}>`; | ||||
|   })} <img${addAttribute(fallbackImage.src, "src")}${spreadAttributes(imgAdditionalAttributes)}${spreadAttributes(fallbackImage.attributes)}> </picture>`; | ||||
| }, "/Users/littlesheep/Documents/Projects/Capital/node_modules/astro/components/Picture.astro", void 0); | ||||
|  | ||||
| const imageConfig = {"service":{"entrypoint":"astro/assets/services/sharp","config":{}},"domains":[],"remotePatterns":[],"endpoint":"astro/assets/endpoint/node"}; | ||||
| 					const assetsDir = new URL("file:///Users/littlesheep/Documents/Projects/Capital/dist/client/"); | ||||
| 					const getImage = async (options) => await getImage$1(options, imageConfig); | ||||
|  | ||||
| async function loadLocalImage(src, url) { | ||||
|   const filePath = new URL("." + src, assetsDir); | ||||
|   let buffer = void 0; | ||||
|   try { | ||||
|     buffer = await readFile(filePath); | ||||
|   } catch (e) { | ||||
|     const sourceUrl = new URL(src, url.origin); | ||||
|     buffer = await loadRemoteImage(sourceUrl); | ||||
|   } | ||||
|   return buffer; | ||||
| } | ||||
| async function loadRemoteImage(src) { | ||||
|   try { | ||||
|     const res = await fetch(src); | ||||
|     if (!res.ok) { | ||||
|       return void 0; | ||||
|     } | ||||
|     return Buffer.from(await res.arrayBuffer()); | ||||
|   } catch (err) { | ||||
|     return void 0; | ||||
|   } | ||||
| } | ||||
| const GET = async ({ request }) => { | ||||
|   try { | ||||
|     const imageService = await getConfiguredImageService(); | ||||
|     if (!("transform" in imageService)) { | ||||
|       throw new Error("Configured image service is not a local service"); | ||||
|     } | ||||
|     const url = new URL(request.url); | ||||
|     const transform = await imageService.parseURL(url, imageConfig); | ||||
|     if (!transform?.src) { | ||||
|       throw new Error("Incorrect transform returned by `parseURL`"); | ||||
|     } | ||||
|     let inputBuffer = void 0; | ||||
|     if (isRemotePath(transform.src)) { | ||||
|       if (isRemoteAllowed(transform.src, imageConfig) === false) { | ||||
|         return new Response("Forbidden", { status: 403 }); | ||||
|       } | ||||
|       inputBuffer = await loadRemoteImage(new URL(transform.src)); | ||||
|     } else { | ||||
|       inputBuffer = await loadLocalImage(transform.src, url); | ||||
|     } | ||||
|     if (!inputBuffer) { | ||||
|       return new Response("Not Found", { status: 404 }); | ||||
|     } | ||||
|     const { data, format } = await imageService.transform(inputBuffer, transform, imageConfig); | ||||
|     return new Response(data, { | ||||
|       status: 200, | ||||
|       headers: { | ||||
|         "Content-Type": mime.getType(format) ?? `image/${format}`, | ||||
|         "Cache-Control": "public, max-age=31536000", | ||||
|         ETag: etag(data.toString()), | ||||
|         Date: (/* @__PURE__ */ new Date()).toUTCString() | ||||
|       } | ||||
|     }); | ||||
|   } catch (err) { | ||||
|     console.error("Could not process image request:", err); | ||||
|     return new Response(`Server Error: ${err}`, { status: 500 }); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| export { GET }; | ||||
							
								
								
									
										31
									
								
								test/data/warden/dist/server/chunks/vnode-children_3wEZly-Z.mjs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								test/data/warden/dist/server/chunks/vnode-children_3wEZly-Z.mjs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| import { parse, DOCUMENT_NODE, ELEMENT_NODE, TEXT_NODE } from 'ultrahtml'; | ||||
| import { createElement, Fragment } from 'react'; | ||||
|  | ||||
| let ids = 0; | ||||
| function convert(children) { | ||||
| 	let doc = parse(children.toString().trim()); | ||||
| 	let id = ids++; | ||||
| 	let key = 0; | ||||
|  | ||||
| 	function createReactElementFromNode(node) { | ||||
| 		const childVnodes = | ||||
| 			Array.isArray(node.children) && node.children.length | ||||
| 				? node.children.map((child) => createReactElementFromNode(child)).filter(Boolean) | ||||
| 				: undefined; | ||||
|  | ||||
| 		if (node.type === DOCUMENT_NODE) { | ||||
| 			return createElement(Fragment, {}, childVnodes); | ||||
| 		} else if (node.type === ELEMENT_NODE) { | ||||
| 			const { class: className, ...props } = node.attributes; | ||||
| 			return createElement(node.name, { ...props, className, key: `${id}-${key++}` }, childVnodes); | ||||
| 		} else if (node.type === TEXT_NODE) { | ||||
| 			// 0-length text gets omitted in JSX | ||||
| 			return node.value.trim() ? node.value : undefined; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	const root = createReactElementFromNode(doc); | ||||
| 	return root.props.children; | ||||
| } | ||||
|  | ||||
| export { convert as default }; | ||||
							
								
								
									
										2355
									
								
								test/data/warden/dist/server/entry.mjs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2355
									
								
								test/data/warden/dist/server/entry.mjs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										197
									
								
								test/data/warden/dist/server/manifest_irk0fM_a.mjs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										197
									
								
								test/data/warden/dist/server/manifest_irk0fM_a.mjs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										263
									
								
								test/data/warden/dist/server/renderers.mjs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										263
									
								
								test/data/warden/dist/server/renderers.mjs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,263 @@ | ||||
| import React, { createElement } from 'react'; | ||||
| import ReactDOM from 'react-dom/server.rs'; | ||||
|  | ||||
| /** | ||||
|  * Astro passes `children` as a string of HTML, so we need | ||||
|  * a wrapper `div` to render that content as VNodes. | ||||
|  * | ||||
|  * As a bonus, we can signal to React that this subtree is | ||||
|  * entirely static and will never change via `shouldComponentUpdate`. | ||||
|  */ | ||||
| const StaticHtml = ({ value, name, hydrate = true }) => { | ||||
| 	if (!value) return null; | ||||
| 	const tagName = hydrate ? 'astro-slot' : 'astro-static-slot'; | ||||
| 	return createElement(tagName, { | ||||
| 		name, | ||||
| 		suppressHydrationWarning: true, | ||||
| 		dangerouslySetInnerHTML: { __html: value }, | ||||
| 	}); | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * This tells React to opt-out of re-rendering this subtree, | ||||
|  * In addition to being a performance optimization, | ||||
|  * this also allows other frameworks to attach to `children`. | ||||
|  * | ||||
|  * See https://preactjs.com/guide/v8/external-dom-mutations | ||||
|  */ | ||||
| StaticHtml.shouldComponentUpdate = () => false; | ||||
|  | ||||
| const contexts = new WeakMap(); | ||||
|  | ||||
| const ID_PREFIX = 'r'; | ||||
|  | ||||
| function getContext(rendererContextResult) { | ||||
| 	if (contexts.has(rendererContextResult)) { | ||||
| 		return contexts.get(rendererContextResult); | ||||
| 	} | ||||
| 	const ctx = { | ||||
| 		currentIndex: 0, | ||||
| 		get id() { | ||||
| 			return ID_PREFIX + this.currentIndex.toString(); | ||||
| 		}, | ||||
| 	}; | ||||
| 	contexts.set(rendererContextResult, ctx); | ||||
| 	return ctx; | ||||
| } | ||||
|  | ||||
| function incrementId(rendererContextResult) { | ||||
| 	const ctx = getContext(rendererContextResult); | ||||
| 	const id = ctx.id; | ||||
| 	ctx.currentIndex++; | ||||
| 	return id; | ||||
| } | ||||
|  | ||||
| const opts = { | ||||
| 						experimentalReactChildren: false | ||||
| 					}; | ||||
|  | ||||
| const slotName = (str) => str.trim().replace(/[-_]([a-z])/g, (_, w) => w.toUpperCase()); | ||||
| const reactTypeof = Symbol.for('react.element'); | ||||
|  | ||||
| function errorIsComingFromPreactComponent(err) { | ||||
| 	return ( | ||||
| 		err.message && | ||||
| 		(err.message.startsWith("Cannot read property '__H'") || | ||||
| 			err.message.includes("(reading '__H')")) | ||||
| 	); | ||||
| } | ||||
|  | ||||
| async function check(Component, props, children) { | ||||
| 	// Note: there are packages that do some unholy things to create "components". | ||||
| 	// Checking the $$typeof property catches most of these patterns. | ||||
| 	if (typeof Component === 'object') { | ||||
| 		return Component['$$typeof'].toString().slice('Symbol('.length).startsWith('react'); | ||||
| 	} | ||||
| 	if (typeof Component !== 'function') return false; | ||||
| 	if (Component.name === 'QwikComponent') return false; | ||||
|  | ||||
| 	// Preact forwarded-ref components can be functions, which React does not support | ||||
| 	if (typeof Component === 'function' && Component['$$typeof'] === Symbol.for('react.forward_ref')) | ||||
| 		return false; | ||||
|  | ||||
| 	if (Component.prototype != null && typeof Component.prototype.render === 'function') { | ||||
| 		return React.Component.isPrototypeOf(Component) || React.PureComponent.isPrototypeOf(Component); | ||||
| 	} | ||||
|  | ||||
| 	let error = null; | ||||
| 	let isReactComponent = false; | ||||
| 	function Tester(...args) { | ||||
| 		try { | ||||
| 			const vnode = Component(...args); | ||||
| 			if (vnode && vnode['$$typeof'] === reactTypeof) { | ||||
| 				isReactComponent = true; | ||||
| 			} | ||||
| 		} catch (err) { | ||||
| 			if (!errorIsComingFromPreactComponent(err)) { | ||||
| 				error = err; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		return React.createElement('div'); | ||||
| 	} | ||||
|  | ||||
| 	await renderToStaticMarkup(Tester, props, children, {}); | ||||
|  | ||||
| 	if (error) { | ||||
| 		throw error; | ||||
| 	} | ||||
| 	return isReactComponent; | ||||
| } | ||||
|  | ||||
| async function getNodeWritable() { | ||||
| 	let nodeStreamBuiltinModuleName = 'node:stream'; | ||||
| 	let { Writable } = await import(/* @vite-ignore */ nodeStreamBuiltinModuleName); | ||||
| 	return Writable; | ||||
| } | ||||
|  | ||||
| function needsHydration(metadata) { | ||||
| 	// Adjust how this is hydrated only when the version of Astro supports `astroStaticSlot` | ||||
| 	return metadata.astroStaticSlot ? !!metadata.hydrate : true; | ||||
| } | ||||
|  | ||||
| async function renderToStaticMarkup(Component, props, { default: children, ...slotted }, metadata) { | ||||
| 	let prefix; | ||||
| 	if (this && this.result) { | ||||
| 		prefix = incrementId(this.result); | ||||
| 	} | ||||
| 	const attrs = { prefix }; | ||||
|  | ||||
| 	delete props['class']; | ||||
| 	const slots = {}; | ||||
| 	for (const [key, value] of Object.entries(slotted)) { | ||||
| 		const name = slotName(key); | ||||
| 		slots[name] = React.createElement(StaticHtml, { | ||||
| 			hydrate: needsHydration(metadata), | ||||
| 			value, | ||||
| 			name, | ||||
| 		}); | ||||
| 	} | ||||
| 	// Note: create newProps to avoid mutating `props` before they are serialized | ||||
| 	const newProps = { | ||||
| 		...props, | ||||
| 		...slots, | ||||
| 	}; | ||||
| 	const newChildren = children ?? props.children; | ||||
| 	if (children && opts.experimentalReactChildren) { | ||||
| 		attrs['data-react-children'] = true; | ||||
| 		const convert = await import('./chunks/vnode-children_3wEZly-Z.mjs').then((mod) => mod.default); | ||||
| 		newProps.children = convert(children); | ||||
| 	} else if (newChildren != null) { | ||||
| 		newProps.children = React.createElement(StaticHtml, { | ||||
| 			hydrate: needsHydration(metadata), | ||||
| 			value: newChildren, | ||||
| 		}); | ||||
| 	} | ||||
| 	const vnode = React.createElement(Component, newProps); | ||||
| 	const renderOptions = { | ||||
| 		identifierPrefix: prefix, | ||||
| 	}; | ||||
| 	let html; | ||||
| 	if (metadata?.hydrate) { | ||||
| 		if ('renderToReadableStream' in ReactDOM) { | ||||
| 			html = await renderToReadableStreamAsync(vnode, renderOptions); | ||||
| 		} else { | ||||
| 			html = await renderToPipeableStreamAsync(vnode, renderOptions); | ||||
| 		} | ||||
| 	} else { | ||||
| 		if ('renderToReadableStream' in ReactDOM) { | ||||
| 			html = await renderToReadableStreamAsync(vnode, renderOptions); | ||||
| 		} else { | ||||
| 			html = await renderToStaticNodeStreamAsync(vnode, renderOptions); | ||||
| 		} | ||||
| 	} | ||||
| 	return { html, attrs }; | ||||
| } | ||||
|  | ||||
| async function renderToPipeableStreamAsync(vnode, options) { | ||||
| 	const Writable = await getNodeWritable(); | ||||
| 	let html = ''; | ||||
| 	return new Promise((resolve, reject) => { | ||||
| 		let error = undefined; | ||||
| 		let stream = ReactDOM.renderToPipeableStream(vnode, { | ||||
| 			...options, | ||||
| 			onError(err) { | ||||
| 				error = err; | ||||
| 				reject(error); | ||||
| 			}, | ||||
| 			onAllReady() { | ||||
| 				stream.pipe( | ||||
| 					new Writable({ | ||||
| 						write(chunk, _encoding, callback) { | ||||
| 							html += chunk.toString('utf-8'); | ||||
| 							callback(); | ||||
| 						}, | ||||
| 						destroy() { | ||||
| 							resolve(html); | ||||
| 						}, | ||||
| 					}) | ||||
| 				); | ||||
| 			}, | ||||
| 		}); | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| async function renderToStaticNodeStreamAsync(vnode, options) { | ||||
| 	const Writable = await getNodeWritable(); | ||||
| 	let html = ''; | ||||
| 	return new Promise((resolve, reject) => { | ||||
| 		let stream = ReactDOM.renderToStaticNodeStream(vnode, options); | ||||
| 		stream.on('error', (err) => { | ||||
| 			reject(err); | ||||
| 		}); | ||||
| 		stream.pipe( | ||||
| 			new Writable({ | ||||
| 				write(chunk, _encoding, callback) { | ||||
| 					html += chunk.toString('utf-8'); | ||||
| 					callback(); | ||||
| 				}, | ||||
| 				destroy() { | ||||
| 					resolve(html); | ||||
| 				}, | ||||
| 			}) | ||||
| 		); | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Use a while loop instead of "for await" due to cloudflare and Vercel Edge issues | ||||
|  * See https://github.com/facebook/react/issues/24169 | ||||
|  */ | ||||
| async function readResult(stream) { | ||||
| 	const reader = stream.getReader(); | ||||
| 	let result = ''; | ||||
| 	const decoder = new TextDecoder('utf-8'); | ||||
| 	while (true) { | ||||
| 		const { done, value } = await reader.read(); | ||||
| 		if (done) { | ||||
| 			if (value) { | ||||
| 				result += decoder.decode(value); | ||||
| 			} else { | ||||
| 				// This closes the decoder | ||||
| 				decoder.decode(new Uint8Array()); | ||||
| 			} | ||||
|  | ||||
| 			return result; | ||||
| 		} | ||||
| 		result += decoder.decode(value, { stream: true }); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| async function renderToReadableStreamAsync(vnode, options) { | ||||
| 	return await readResult(await ReactDOM.renderToReadableStream(vnode, options)); | ||||
| } | ||||
|  | ||||
| const _renderer0 = { | ||||
| 	check, | ||||
| 	renderToStaticMarkup, | ||||
| 	supportsAstroStaticSlot: true, | ||||
| }; | ||||
|  | ||||
| const renderers = [Object.assign({"name":"@astrojs/react","clientEntrypoint":"@astrojs/react/client.js","serverEntrypoint":"@astrojs/react/server.rs.js"}, { ssr: _renderer0 }),]; | ||||
|  | ||||
| export { renderers }; | ||||
		Reference in New Issue
	
	Block a user