♻️ 使用 Actix RS 重构 #8
| @@ -5,24 +5,24 @@ on: | |||||||
|     branches: [ master ] |     branches: [ master ] | ||||||
| 
 | 
 | ||||||
| jobs: | jobs: | ||||||
|   build-docker: |   build-image: | ||||||
|     runs-on: edge |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - name: Checkout |       - name: Checkout | ||||||
|         uses: actions/checkout@v3 |         uses: actions/checkout@v4 | ||||||
|       - name: Set up QEMU |       - name: Set up QEMU | ||||||
|         uses: docker/setup-qemu-action@v2 |         uses: docker/setup-qemu-action@v3 | ||||||
|       - name: Set up Docker Buildx |       - name: Set up Docker Buildx | ||||||
|         uses: docker/setup-buildx-action@v2 |         uses: docker/setup-buildx-action@v3 | ||||||
|       - name: Login to Docker Hub |       - name: Login to Docker Hub | ||||||
|         uses: docker/login-action@v2 |         uses: docker/login-action@v3 | ||||||
|         with: |         with: | ||||||
|           username: ${{ secrets.DOCKER_REGISTRY_USERNAME }} |           username: ${{ secrets.DOCKER_REGISTRY_USERNAME }} | ||||||
|           password: ${{ secrets.DOCKER_REGISTRY_TOKEN }} |           password: ${{ secrets.DOCKER_REGISTRY_TOKEN }} | ||||||
|       - name: Build and push |       - name: Build and push | ||||||
|         uses: docker/build-push-action@v4 |         uses: docker/build-push-action@v5 | ||||||
|         with: |         with: | ||||||
|           context: . |           context: . | ||||||
|           file: ./Dockerfile |  | ||||||
|           push: true |           push: true | ||||||
|           tags: xsheep2010/roadsign:nightly |           file: ./Dockerfile | ||||||
|  |           tags: xsheep2010/roadsign:sigma | ||||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,4 +1,5 @@ | |||||||
| /config | /config | ||||||
|  | /certs | ||||||
| /letsencrypt | /letsencrypt | ||||||
|  |  | ||||||
| # Added by cargo | # 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 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||||||
|  |  | ||||||
| [dependencies] | [dependencies] | ||||||
| config = { version = "0.13.4", features = ["toml"] } | actix-files = "0.6.5" | ||||||
| futures-util = "0.3.30" | actix-proxy = "0.2.0" | ||||||
| http = "1.0.0" | actix-web = { version = "4.5.1", features = ["rustls-0_22"] } | ||||||
| hyper-util = { version = "0.1.2", features = ["full"] } | actix-web-httpauth = "0.8.1" | ||||||
|  | awc = "3.4.0" | ||||||
|  | config = { version = "0.14.0", features = ["toml"] } | ||||||
| lazy_static = "1.4.0" | lazy_static = "1.4.0" | ||||||
| mime = "0.3.17" | mime = "0.3.17" | ||||||
| percent-encoding = "2.3.1" | 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" | queryst = "3.0.0" | ||||||
| rand = "0.8.5" | rand = "0.8.5" | ||||||
| regex = "1.10.2" | regex = "1.10.2" | ||||||
| reqwest = { git = "https://github.com/seanmonstar/reqwest.git", branch = "hyper-v1", version = "0.11.23" } |  | ||||||
| serde = "1.0.195" | serde = "1.0.195" | ||||||
| serde_json = "1.0.111" | serde_json = "1.0.111" | ||||||
| tokio = { version = "1.35.1", features = [ | tokio = { version = "1.35.1", features = [ | ||||||
| @@ -37,3 +31,6 @@ toml = "0.8.8" | |||||||
| tracing = "0.1.40" | tracing = "0.1.40" | ||||||
| tracing-subscriber = "0.3.18" | tracing-subscriber = "0.3.18" | ||||||
| wildmatch = "2.3.0" | 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 install | ||||||
| RUN npm run build | RUN npm run build | ||||||
| WORKDIR /source | 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 | # Runtime | ||||||
| FROM golang:alpine | FROM golang:alpine | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								README.md
									
									
									
									
									
								
							| @@ -74,9 +74,9 @@ rds cli with this command. | |||||||
|  |  | ||||||
| ```shell | ```shell | ||||||
| rds connect <id> <url> <password> | rds connect <id> <url> <password> | ||||||
| # ID will allow you find this server in after commands. | # ID will allow you find this server.rs in after commands. | ||||||
| # URL is to your roadsign server sideload api. | # URL is to your roadsign server.rs sideload api. | ||||||
| # Password is your roadsign server credential. | # Password is your roadsign server.rs credential. | ||||||
| # ====================================================================== | # ====================================================================== | ||||||
| # !WARNING! All these things will storage in your $HOME/.roadsignrc.yaml | # !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. | Then, sync your local config to remote. | ||||||
|  |  | ||||||
| ```shell | ```shell | ||||||
| rds sync <server id> <site id> <config file> | rds sync <server.rs id> <site id> <config file> | ||||||
| # Server ID is your server added by last command. | # 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. | # Site ID is your new site id or old site id if you need update it. | ||||||
| # Config File is your local config file path. | # Config File is your local config file path. | ||||||
| ``` | ``` | ||||||
|   | |||||||
| @@ -1,7 +1,17 @@ | |||||||
| regions = "./regions" | regions = "./regions" | ||||||
| secret = "aEXcED5xJ3" | secret = "aEXcED5xJ3" | ||||||
|  |  | ||||||
| [listen] | [sideload] | ||||||
| proxies = "0.0.0.0:80" | bind_addr = "0.0.0.0:81" | ||||||
| proxies_tls = "0.0.0.0:443" |  | ||||||
| sideload = "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" | ||||||
| @@ -11,4 +11,4 @@ uri = "files://regions?index=index.html" | |||||||
| [[applications]] | [[applications]] | ||||||
| id = "script" | id = "script" | ||||||
| exe = "./script.sh" | exe = "./script.sh" | ||||||
| workdir = "regions" | workdir = "regions" | ||||||
							
								
								
									
										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; | pub mod loader; | ||||||
|  |  | ||||||
| lazy_static! { | 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 config; | ||||||
| mod proxies; | mod proxies; | ||||||
| mod sideload; | mod sideload; | ||||||
| pub mod warden; | mod warden; | ||||||
|  | mod server; | ||||||
|  | pub mod tls; | ||||||
|  |  | ||||||
|  | use std::error; | ||||||
| use lazy_static::lazy_static; | use lazy_static::lazy_static; | ||||||
| use poem::{listener::TcpListener, EndpointExt, Route, Server}; |  | ||||||
| use poem_openapi::OpenApiService; |  | ||||||
| use proxies::RoadInstance; | use proxies::RoadInstance; | ||||||
| use tokio::sync::Mutex; | use tokio::sync::Mutex; | ||||||
|  | use tokio::task::JoinSet; | ||||||
| use tracing::{error, info, Level}; | use tracing::{error, info, Level}; | ||||||
|  | use crate::proxies::server::build_proxies; | ||||||
| use crate::proxies::route; | use crate::sideload::server::build_sideload; | ||||||
|  |  | ||||||
| lazy_static! { | lazy_static! { | ||||||
|     static ref ROAD: Mutex<RoadInstance> = Mutex::new(RoadInstance::new()); |     static ref ROAD: Mutex<RoadInstance> = Mutex::new(RoadInstance::new()); | ||||||
| } | } | ||||||
|  |  | ||||||
| #[tokio::main] | #[tokio::main] | ||||||
| async fn main() -> Result<(), std::io::Error> { | async fn main() -> Result<(), Box<dyn error::Error>> { | ||||||
|     // Setting up logging |     // Setting up logging | ||||||
|     if std::env::var_os("RUST_LOG").is_none() { |  | ||||||
|         std::env::set_var("RUST_LOG", "poem=debug"); |  | ||||||
|     } |  | ||||||
|     tracing_subscriber::fmt() |     tracing_subscriber::fmt() | ||||||
|         .with_max_level(Level::DEBUG) |         .with_max_level(Level::DEBUG) | ||||||
|         .init(); |         .init(); | ||||||
| @@ -30,11 +30,10 @@ async fn main() -> Result<(), std::io::Error> { | |||||||
|     // Prepare all the stuff |     // Prepare all the stuff | ||||||
|     info!("Loading proxy regions..."); |     info!("Loading proxy regions..."); | ||||||
|     match proxies::loader::scan_regions( |     match proxies::loader::scan_regions( | ||||||
|         config::C |         config::CFG | ||||||
|             .read() |             .read() | ||||||
|             .await |             .await | ||||||
|             .get_string("regions") |             .get_string("regions")? | ||||||
|             .unwrap_or("./regions".to_string()), |  | ||||||
|     ) { |     ) { | ||||||
|         Err(_) => error!("Loading proxy regions... failed"), |         Err(_) => error!("Loading proxy regions... failed"), | ||||||
|         Ok((regions, count)) => { |         Ok((regions, count)) => { | ||||||
| @@ -43,37 +42,15 @@ async fn main() -> Result<(), std::io::Error> { | |||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|  |     let mut server_set = JoinSet::new(); | ||||||
|  |  | ||||||
|     // Proxies |     // Proxies | ||||||
|     let proxies_server = Server::new(TcpListener::bind( |     for server in build_proxies().await? { | ||||||
|         config::C |         server_set.spawn(server); | ||||||
|             .read() |     } | ||||||
|             .await |  | ||||||
|             .get_string("listen.proxies") |  | ||||||
|             .unwrap_or("0.0.0.0:80".to_string()), |  | ||||||
|     )) |  | ||||||
|     .run(route::handle); |  | ||||||
|  |  | ||||||
|     // Sideload |     // Sideload | ||||||
|     let sideload = OpenApiService::new(sideload::SideloadApi, "Sideload API", "1.0") |     server_set.spawn(build_sideload().await?); | ||||||
|         .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()), |  | ||||||
|         }), |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|     // Process manager |     // Process manager | ||||||
|     { |     { | ||||||
| @@ -85,7 +62,8 @@ async fn main() -> Result<(), std::io::Error> { | |||||||
|         app.warden.start().await; |         app.warden.start().await; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     tokio::try_join!(proxies_server, sideload_server)?; |     // Wait for web servers | ||||||
|  |     server_set.join_next().await; | ||||||
|  |  | ||||||
|     Ok(()) |     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 std::collections::HashMap; | ||||||
|  |  | ||||||
| use poem_openapi::Object; |  | ||||||
| use queryst::parse; | use queryst::parse; | ||||||
| use serde::{Deserialize, Serialize}; | use serde::{Deserialize, Serialize}; | ||||||
| use serde_json::json; | use serde_json::json; | ||||||
| @@ -9,14 +8,14 @@ use crate::warden::Application; | |||||||
|  |  | ||||||
| use super::responder::StaticResponderConfig; | use super::responder::StaticResponderConfig; | ||||||
|  |  | ||||||
| #[derive(Debug, Object, Clone, Serialize, Deserialize)] | #[derive(Debug, Clone, Serialize, Deserialize)] | ||||||
| pub struct Region { | pub struct Region { | ||||||
|     pub id: String, |     pub id: String, | ||||||
|     pub locations: Vec<Location>, |     pub locations: Vec<Location>, | ||||||
|     pub applications: Vec<Application>, |     pub applications: Vec<Application>, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Object, Clone, Serialize, Deserialize)] | #[derive(Debug, Clone, Serialize, Deserialize)] | ||||||
| pub struct Location { | pub struct Location { | ||||||
|     pub id: String, |     pub id: String, | ||||||
|     pub hosts: Vec<String>, |     pub hosts: Vec<String>, | ||||||
| @@ -27,7 +26,7 @@ pub struct Location { | |||||||
|     pub destinations: Vec<Destination>, |     pub destinations: Vec<Destination>, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Object, Clone, Serialize, Deserialize)] | #[derive(Debug, Clone, Serialize, Deserialize)] | ||||||
| pub struct Destination { | pub struct Destination { | ||||||
|     pub id: String, |     pub id: String, | ||||||
|     pub uri: String, |     pub uri: String, | ||||||
| @@ -75,15 +74,6 @@ impl Destination { | |||||||
|         .collect::<Vec<_>>()[0] |         .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, ()> { |     pub fn get_hypertext_uri(&self) -> Result<String, ()> { | ||||||
|         match self.get_protocol() { |         match self.get_protocol() { | ||||||
|             "http" => Ok("http://".to_string() + self.get_host()), |             "http" => Ok("http://".to_string() + self.get_host()), | ||||||
|   | |||||||
| @@ -1,21 +1,30 @@ | |||||||
| use std::collections::VecDeque; | use std::collections::VecDeque; | ||||||
|  |  | ||||||
| use poem_openapi::Object; |  | ||||||
| use serde::{Deserialize, Serialize}; | use serde::{Deserialize, Serialize}; | ||||||
|  |  | ||||||
| use super::config::{Destination, Location, Region}; | use super::config::{Destination, Location, Region}; | ||||||
|  |  | ||||||
| #[derive(Debug, Object, Clone, Serialize, Deserialize, PartialEq)] | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] | ||||||
| pub struct RoadTrace { | pub struct RoadTrace { | ||||||
|     pub region: String, |     pub region: String, | ||||||
|     pub location: String, |     pub location: String, | ||||||
|     pub destination: String, |     pub destination: String, | ||||||
|  |     pub ip_address: String, | ||||||
|  |     pub user_agent: String, | ||||||
|     pub error: Option<String>, |     pub error: Option<String>, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl RoadTrace { | 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 { |         RoadTrace { | ||||||
|  |             ip_address: ip, | ||||||
|  |             user_agent: ua, | ||||||
|             region: reg.id, |             region: reg.id, | ||||||
|             location: loc.id, |             location: loc.id, | ||||||
|             destination: end.id, |             destination: end.id, | ||||||
| @@ -24,17 +33,16 @@ impl RoadTrace { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn from_structs_with_error( |     pub fn from_structs_with_error( | ||||||
|  |         ip: String, | ||||||
|  |         ua: String, | ||||||
|         reg: Region, |         reg: Region, | ||||||
|         loc: Location, |         loc: Location, | ||||||
|         end: Destination, |         end: Destination, | ||||||
|         err: String, |         err: String, | ||||||
|     ) -> RoadTrace { |     ) -> RoadTrace { | ||||||
|         RoadTrace { |         let mut trace = Self::from_structs(ip, ua, reg, loc, end); | ||||||
|             region: reg.id, |         trace.error = Some(err); | ||||||
|             location: loc.id, |         return trace; | ||||||
|             destination: end.id, |  | ||||||
|             error: Some(err), |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -47,7 +55,7 @@ pub struct RoadMetrics { | |||||||
|     pub recent_errors: VecDeque<RoadTrace>, |     pub recent_errors: VecDeque<RoadTrace>, | ||||||
| } | } | ||||||
|  |  | ||||||
| const MAX_TRACE_COUNT: usize = 10; | const MAX_TRACE_COUNT: usize = 32; | ||||||
|  |  | ||||||
| impl RoadMetrics { | impl RoadMetrics { | ||||||
|     pub fn new() -> 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.requests_count += 1; | ||||||
|         self.recent_successes |         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 { |         if self.recent_successes.len() > MAX_TRACE_COUNT { | ||||||
|             self.recent_successes.pop_front(); |             self.recent_successes.pop_front(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn add_faliure_request( |     pub fn add_failure_request( | ||||||
|         &mut self, |         &mut self, | ||||||
|  |         ip: String, | ||||||
|  |         ua: String, | ||||||
|         reg: Region, |         reg: Region, | ||||||
|         loc: Location, |         loc: Location, | ||||||
|         end: Destination, |         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.requests_count += 1; | ||||||
|         self.failures_count += 1; |         self.failures_count += 1; | ||||||
|         self.recent_errors |         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 { |         if self.recent_errors.len() > MAX_TRACE_COUNT { | ||||||
|             self.recent_errors.pop_front(); |             self.recent_errors.pop_front(); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -1,7 +1,9 @@ | |||||||
| use http::Method; | use actix_web::http::header::{ContentType, HeaderMap}; | ||||||
| use poem::http::{HeaderMap, Uri}; | use actix_web::http::{Method, StatusCode, Uri}; | ||||||
| use regex::Regex; | use regex::Regex; | ||||||
| use wildmatch::WildMatch; | use wildmatch::WildMatch; | ||||||
|  | use actix_web::{error, HttpResponse}; | ||||||
|  | use derive_more::{Display}; | ||||||
|  |  | ||||||
| use crate::warden::WardenInstance; | use crate::warden::WardenInstance; | ||||||
|  |  | ||||||
| @@ -10,12 +12,52 @@ use self::{ | |||||||
|     metrics::RoadMetrics, |     metrics::RoadMetrics, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| pub mod browser; |  | ||||||
| pub mod config; | pub mod config; | ||||||
| pub mod loader; | pub mod loader; | ||||||
| pub mod metrics; | pub mod metrics; | ||||||
| pub mod responder; | pub mod responder; | ||||||
| pub mod route; | 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)] | #[derive(Debug, Clone)] | ||||||
| pub struct RoadInstance { | pub struct RoadInstance { | ||||||
| @@ -38,7 +80,7 @@ impl RoadInstance { | |||||||
|     pub fn filter( |     pub fn filter( | ||||||
|         &self, |         &self, | ||||||
|         uri: &Uri, |         uri: &Uri, | ||||||
|         method: Method, |         method: &Method, | ||||||
|         headers: &HeaderMap, |         headers: &HeaderMap, | ||||||
|     ) -> Option<(&Region, &Location)> { |     ) -> Option<(&Region, &Location)> { | ||||||
|         self.regions.iter().find_map(|region| { |         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::{ | use std::{ | ||||||
|     ffi::OsStr, |     ffi::OsStr, | ||||||
|     path::{Path, PathBuf}, |     path::{Path, PathBuf}, | ||||||
|     sync::Arc, |  | ||||||
| }; | }; | ||||||
| use tokio::sync::RwLock; | use actix_files::{NamedFile}; | ||||||
| use tokio_tungstenite::connect_async; | use actix_proxy::IntoHttpResponse; | ||||||
|  | use actix_web::{HttpRequest, HttpResponse, web}; | ||||||
| use super::browser::{DirectoryTemplate, FileRef}; | use actix_web::http::Method; | ||||||
|  | use awc::Client; | ||||||
| lazy_static! { | use tracing::log::warn; | ||||||
|     pub static ref CLIENT: reqwest::Client = reqwest::Client::new(); | use crate::proxies::ProxyError; | ||||||
| } |  | ||||||
|  |  | ||||||
| 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() |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub async fn respond_hypertext( | pub async fn respond_hypertext( | ||||||
|     uri: String, |     uri: String, | ||||||
|     ori: &Uri, |     req: HttpRequest, | ||||||
|     req: &Request, |     client: web::Data<Client>, | ||||||
|     method: Method, | ) -> Result<HttpResponse, ProxyError> { | ||||||
|     body: Body, |     let ip = req.peer_addr().unwrap().ip().to_string(); | ||||||
|     headers: &HeaderMap, |  | ||||||
| ) -> Result<Response, Error> { |  | ||||||
|     let ip = req.remote_addr().to_string(); |  | ||||||
|     let proto = req.uri().scheme_str().unwrap(); |     let proto = req.uri().scheme_str().unwrap(); | ||||||
|     let host = req.uri().host().unwrap(); |     let host = req.uri().host().unwrap(); | ||||||
|  |  | ||||||
|     let mut headers = headers.clone(); |     let mut headers = req.headers().clone(); | ||||||
|     headers.insert("Server", "RoadSign".parse().unwrap()); |     headers.insert("Server".parse().unwrap(), "RoadSign".parse().unwrap()); | ||||||
|     headers.insert("X-Forward-For", ip.parse().unwrap()); |     headers.insert("X-Forward-For".parse().unwrap(), ip.parse().unwrap()); | ||||||
|     headers.insert("X-Forwarded-Proto", proto.parse().unwrap()); |     headers.insert("X-Forwarded-Proto".parse().unwrap(), proto.parse().unwrap()); | ||||||
|     headers.insert("X-Forwarded-Host", host.parse().unwrap()); |     headers.insert("X-Forwarded-Host".parse().unwrap(), host.parse().unwrap()); | ||||||
|     headers.insert("X-Real-IP", ip.parse().unwrap()); |     headers.insert("X-Real-IP".parse().unwrap(), ip.parse().unwrap()); | ||||||
|     headers.insert( |     headers.insert( | ||||||
|         "Forwarded", |         "Forwarded".parse().unwrap(), | ||||||
|         format!("by={};for={};host={};proto={}", ip, ip, host, proto) |         format!("by={};for={};host={};proto={}", ip, ip, host, proto) | ||||||
|             .parse() |             .parse() | ||||||
|             .unwrap(), |             .unwrap(), | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|     let res = CLIENT |     let res = client.get(uri).send().await; | ||||||
|         .request(method, uri + ori.path() + ori.query().unwrap_or("")) |  | ||||||
|         .headers(headers.clone()) |  | ||||||
|         .body(body.into_bytes().await.unwrap()) |  | ||||||
|         .send() |  | ||||||
|         .await; |  | ||||||
|  |  | ||||||
|     match res { |     return match res { | ||||||
|         Ok(result) => { |         Ok(result) => { | ||||||
|             let mut res = Response::default(); |             let mut res = result.into_http_response(); | ||||||
|             res.extensions().clone_from(&result.extensions()); |             res.headers_mut().insert("Server".parse().unwrap(), "RoadSign".parse().unwrap()); | ||||||
|             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()); |  | ||||||
|             Ok(res) |             Ok(res) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         Err(error) => Err(Error::from_string( |         Err(error) => { | ||||||
|             error.to_string(), |             warn!("Proxy got a upstream issue... {:?}", error); | ||||||
|             error.status().unwrap_or(StatusCode::BAD_GATEWAY), |             Err(ProxyError::BadGateway) | ||||||
|         )), |         } | ||||||
|     } |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
| pub struct StaticResponderConfig { | pub struct StaticResponderConfig { | ||||||
| @@ -126,14 +60,10 @@ pub struct StaticResponderConfig { | |||||||
|  |  | ||||||
| pub async fn respond_static( | pub async fn respond_static( | ||||||
|     cfg: StaticResponderConfig, |     cfg: StaticResponderConfig, | ||||||
|     method: Method, |     req: HttpRequest, | ||||||
|     req: &Request, | ) -> Result<HttpResponse, ProxyError> { | ||||||
| ) -> Result<Response, Error> { |     if req.method() != Method::GET { | ||||||
|     if method != Method::GET { |         return Err(ProxyError::MethodGetOnly); | ||||||
|         return Err(Error::from_string( |  | ||||||
|             "This destination only support GET request.", |  | ||||||
|             StatusCode::METHOD_NOT_ALLOWED, |  | ||||||
|         )); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     let path = req |     let path = req | ||||||
| @@ -142,9 +72,12 @@ pub async fn respond_static( | |||||||
|         .trim_start_matches('/') |         .trim_start_matches('/') | ||||||
|         .trim_end_matches('/'); |         .trim_end_matches('/'); | ||||||
|  |  | ||||||
|     let path = percent_encoding::percent_decode_str(path) |     let path = match percent_encoding::percent_decode_str(path).decode_utf8() { | ||||||
|         .decode_utf8() |         Ok(val) => val, | ||||||
|         .map_err(|_| Error::from_status(StatusCode::NOT_FOUND))?; |         Err(_) => { | ||||||
|  |             return Err(ProxyError::NotFound); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  |  | ||||||
|     let base_path = cfg.uri.parse::<PathBuf>().unwrap(); |     let base_path = cfg.uri.parse::<PathBuf>().unwrap(); | ||||||
|     let mut file_path = base_path.clone(); |     let mut file_path = base_path.clone(); | ||||||
| @@ -159,7 +92,7 @@ pub async fn respond_static( | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     if !file_path.starts_with(cfg.uri) { |     if !file_path.starts_with(cfg.uri) { | ||||||
|         return Err(Error::from_status(StatusCode::FORBIDDEN)); |         return Err(ProxyError::InvalidRequestPath); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if !file_path.exists() { |     if !file_path.exists() { | ||||||
| @@ -172,87 +105,30 @@ pub async fn respond_static( | |||||||
|             file_path.pop(); |             file_path.pop(); | ||||||
|             file_path.push((file_name + &suffix).as_str()); |             file_path.push((file_name + &suffix).as_str()); | ||||||
|             if file_path.is_file() { |             if file_path.is_file() { | ||||||
|                 return Ok(StaticFileRequest::from_request_without_body(req) |                 return Ok(NamedFile::open(file_path).unwrap().into_response(&req)); | ||||||
|                     .await? |  | ||||||
|                     .create_response(&file_path, cfg.utf8)? |  | ||||||
|                     .into_response()); |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if let Some(file) = cfg.fallback { |         if let Some(file) = cfg.fallback { | ||||||
|             let fallback_path = base_path.join(file); |             let fallback_path = base_path.join(file); | ||||||
|             if fallback_path.is_file() { |             if fallback_path.is_file() { | ||||||
|                 return Ok(StaticFileRequest::from_request_without_body(req) |                 return Ok(NamedFile::open(fallback_path).unwrap().into_response(&req)); | ||||||
|                     .await? |  | ||||||
|                     .create_response(&fallback_path, cfg.utf8)? |  | ||||||
|                     .into_response()); |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         return Err(Error::from_status(StatusCode::NOT_FOUND)); |  | ||||||
|  |         return Err(ProxyError::NotFound); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if file_path.is_file() { |     return if file_path.is_file() { | ||||||
|         Ok(StaticFileRequest::from_request_without_body(req) |         Ok(NamedFile::open(file_path).unwrap().into_response(&req)) | ||||||
|             .await? |  | ||||||
|             .create_response(&file_path, cfg.utf8)? |  | ||||||
|             .into_response()) |  | ||||||
|     } else { |     } 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 { |         if let Some(index_file) = &cfg.index { | ||||||
|             let index_path = file_path.join(index_file); |             let index_path = file_path.join(index_file); | ||||||
|             if index_path.is_file() { |             if index_path.is_file() { | ||||||
|                 return Ok(StaticFileRequest::from_request_without_body(req) |                 return Ok(NamedFile::open(index_path).unwrap().into_response(&req)); | ||||||
|                     .await? |  | ||||||
|                     .create_response(&index_path, cfg.utf8)? |  | ||||||
|                     .into_response()); |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if cfg.browse { |         return Err(ProxyError::NotFound); | ||||||
|             let read_dir = file_path |     }; | ||||||
|                 .read_dir() |  | ||||||
|                 .map_err(|_| Error::from_status(StatusCode::FORBIDDEN))?; |  | ||||||
|             let mut template = DirectoryTemplate { |  | ||||||
|                 path: &path, |  | ||||||
|                 files: Vec::new(), |  | ||||||
|             }; |  | ||||||
|  |  | ||||||
|             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 actix_web::{HttpRequest, HttpResponse, ResponseError, web}; | ||||||
| use poem::{ | use actix_web::http::header; | ||||||
|     handler, | use awc::Client; | ||||||
|     http::{HeaderMap, StatusCode, Uri}, |  | ||||||
|     web::websocket::WebSocket, |  | ||||||
|     Body, Error, FromRequest, IntoResponse, Request, Response, Result, |  | ||||||
| }; |  | ||||||
| use rand::seq::SliceRandom; | use rand::seq::SliceRandom; | ||||||
|  |  | ||||||
| use crate::{ | use crate::{ | ||||||
| @@ -14,23 +10,14 @@ use crate::{ | |||||||
|     }, |     }, | ||||||
|     ROAD, |     ROAD, | ||||||
| }; | }; | ||||||
|  | use crate::proxies::ProxyError; | ||||||
|  |  | ||||||
| #[handler] | pub async fn handle(req: HttpRequest, client: web::Data<Client>) -> HttpResponse { | ||||||
| pub async fn handle( |  | ||||||
|     req: &Request, |  | ||||||
|     uri: &Uri, |  | ||||||
|     headers: &HeaderMap, |  | ||||||
|     method: Method, |  | ||||||
|     body: Body, |  | ||||||
| ) -> Result<impl IntoResponse, Error> { |  | ||||||
|     let readable_app = ROAD.lock().await; |     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, |         Some(val) => val, | ||||||
|         None => { |         None => { | ||||||
|             return Err(Error::from_string( |             return ProxyError::NoGateway.error_response(); | ||||||
|                 "There are no region be able to respone this request.", |  | ||||||
|                 StatusCode::NOT_FOUND, |  | ||||||
|             )) |  | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
| @@ -41,58 +28,26 @@ pub async fn handle( | |||||||
|  |  | ||||||
|     async fn forward( |     async fn forward( | ||||||
|         end: &Destination, |         end: &Destination, | ||||||
|         req: &Request, |         req: HttpRequest, | ||||||
|         ori: &Uri, |         client: web::Data<Client>, | ||||||
|         headers: &HeaderMap, |     ) -> Result<HttpResponse, ProxyError> { | ||||||
|         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); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Handle normal web request |         // Handle normal web request | ||||||
|         match end.get_type() { |         match end.get_type() { | ||||||
|             DestinationType::Hypertext => { |             DestinationType::Hypertext => { | ||||||
|                 let Ok(uri) = end.get_hypertext_uri() else { |                 let Ok(uri) = end.get_hypertext_uri() else { | ||||||
|                     return Err(Error::from_string( |                     return Err(ProxyError::NotImplemented); | ||||||
|                         "This destination was not support web requests.", |  | ||||||
|                         StatusCode::NOT_IMPLEMENTED, |  | ||||||
|                     )); |  | ||||||
|                 }; |                 }; | ||||||
|  |  | ||||||
|                 responder::respond_hypertext(uri, ori, req, method, body, headers).await |                 responder::respond_hypertext(uri, req, client).await | ||||||
|             } |             } | ||||||
|             DestinationType::StaticFiles => { |             DestinationType::StaticFiles => { | ||||||
|                 let Ok(cfg) = end.get_static_config() else { |                 let Ok(cfg) = end.get_static_config() else { | ||||||
|                     return Err(Error::from_string( |                     return Err(ProxyError::NotImplemented); | ||||||
|                         "This destination was not support static files.", |  | ||||||
|                         StatusCode::NOT_IMPLEMENTED, |  | ||||||
|                     )); |  | ||||||
|                 }; |                 }; | ||||||
|  |  | ||||||
|                 responder::respond_static(cfg, method, req).await |                 responder::respond_static(cfg, req).await | ||||||
|             } |             } | ||||||
|             _ => Err(Error::from_string( |             _ => Err(ProxyError::NotImplemented) | ||||||
|                 "Unsupported destination protocol.", |  | ||||||
|                 StatusCode::NOT_IMPLEMENTED, |  | ||||||
|             )), |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -100,23 +55,32 @@ pub async fn handle( | |||||||
|     let loc = location.clone(); |     let loc = location.clone(); | ||||||
|     let end = destination.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) => { |         Ok(resp) => { | ||||||
|             tokio::spawn(async move { |             tokio::spawn(async move { | ||||||
|                 let writable_app = &mut ROAD.lock().await; |                 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) => { |         Err(resp) => { | ||||||
|             let message = format!("{:}", err); |             let message = resp.to_string(); | ||||||
|             tokio::spawn(async move { |             tokio::spawn(async move { | ||||||
|                 let writable_app = &mut ROAD.lock().await; |                 let writable_app = &mut ROAD.lock().await; | ||||||
|                 writable_app |                 writable_app | ||||||
|                     .metrics |                     .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; | mod overview; | ||||||
| pub mod regions; | mod regions; | ||||||
|  | pub mod server; | ||||||
|  |  | ||||||
| pub struct SideloadApi; | static ROOT: &str = ""; | ||||||
|  |  | ||||||
| #[OpenApi] | pub fn service() -> Scope { | ||||||
| impl SideloadApi { |     web::scope("/cgi") | ||||||
|     #[oai(path = "/", method = "get")] |         .route(ROOT, web::get().to(get_overview)) | ||||||
|     async fn index(&self) -> overview::OverviewResponse { |         .route("/regions", web::get().to(list_region)) | ||||||
|         overview::index().await | } | ||||||
|     } |  | ||||||
|  |  | ||||||
|     #[oai(path = "/regions", method = "get")] |  | ||||||
|     async fn regions_index(&self) -> regions::RegionResponse { |  | ||||||
|         regions::index().await |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -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::{ | #[derive(Debug, Clone, PartialEq, Serialize)] | ||||||
|     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)] |  | ||||||
| pub struct OverviewData { | pub struct OverviewData { | ||||||
|     /// Loaded regions count |  | ||||||
|     #[oai(read_only)] |  | ||||||
|     regions: usize, |     regions: usize, | ||||||
|     /// Loaded locations count |  | ||||||
|     #[oai(read_only)] |  | ||||||
|     locations: usize, |     locations: usize, | ||||||
|     /// Loaded destnations count |  | ||||||
|     #[oai(read_only)] |  | ||||||
|     destinations: usize, |     destinations: usize, | ||||||
|     /// Recent requests count |  | ||||||
|     requests_count: u64, |     requests_count: u64, | ||||||
|     /// Recent requests success count |     failures_count: u64, | ||||||
|     faliures_count: u64, |  | ||||||
|     /// Recent requests falied count |  | ||||||
|     successes_count: u64, |     successes_count: u64, | ||||||
|     /// Recent requests success rate |  | ||||||
|     success_rate: f64, |     success_rate: f64, | ||||||
|     /// Recent successes |  | ||||||
|     recent_successes: Vec<RoadTrace>, |     recent_successes: Vec<RoadTrace>, | ||||||
|     /// Recent errors |  | ||||||
|     recent_errors: Vec<RoadTrace>, |     recent_errors: Vec<RoadTrace>, | ||||||
| } | } | ||||||
|  |  | ||||||
| pub async fn index() -> OverviewResponse { | pub async fn get_overview() -> web::Json<OverviewData> { | ||||||
|     let locked_app = ROAD.lock().await; |     let locked_app = ROAD.lock().await; | ||||||
|     let regions = locked_app.regions.clone(); |     let regions = locked_app.regions.clone(); | ||||||
|     let locations = regions |     let locations = regions | ||||||
| @@ -51,13 +28,13 @@ pub async fn index() -> OverviewResponse { | |||||||
|         .iter() |         .iter() | ||||||
|         .flat_map(|item| item.destinations.clone()) |         .flat_map(|item| item.destinations.clone()) | ||||||
|         .collect::<Vec<Destination>>(); |         .collect::<Vec<Destination>>(); | ||||||
|     OverviewResponse::Ok(Json(OverviewData { |     web::Json(OverviewData { | ||||||
|         regions: regions.len(), |         regions: regions.len(), | ||||||
|         locations: locations.len(), |         locations: locations.len(), | ||||||
|         destinations: destinations.len(), |         destinations: destinations.len(), | ||||||
|         requests_count: locked_app.metrics.requests_count, |         requests_count: locked_app.metrics.requests_count, | ||||||
|         successes_count: locked_app.metrics.requests_count - locked_app.metrics.failures_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(), |         success_rate: locked_app.metrics.get_success_rate(), | ||||||
|         recent_successes: locked_app |         recent_successes: locked_app | ||||||
|             .metrics |             .metrics | ||||||
| @@ -71,5 +48,5 @@ pub async fn index() -> OverviewResponse { | |||||||
|             .clone() |             .clone() | ||||||
|             .into_iter() |             .into_iter() | ||||||
|             .collect::<Vec<_>>(), |             .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}; | pub async fn list_region() -> web::Json<Vec<Region>> { | ||||||
|  |  | ||||||
| #[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 { |  | ||||||
|     let locked_app = ROAD.lock().await; |     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 std::collections::HashMap; | ||||||
|  |  | ||||||
| use futures_util::lock::Mutex; |  | ||||||
| use lazy_static::lazy_static; | use lazy_static::lazy_static; | ||||||
| use poem_openapi::Object; |  | ||||||
| use serde::{Deserialize, Serialize}; | use serde::{Deserialize, Serialize}; | ||||||
|  | use tokio::sync::Mutex; | ||||||
| use tracing::{debug, warn}; | use tracing::{debug, warn}; | ||||||
|  |  | ||||||
| use crate::proxies::config::Region; | 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 struct Application { | ||||||
|     pub id: String, |     pub id: String, | ||||||
|     pub exe: String, |     pub exe: String, | ||||||
|   | |||||||
| @@ -1,12 +1,12 @@ | |||||||
| use std::{borrow::BorrowMut, collections::HashMap, io}; | use std::{borrow::BorrowMut, collections::HashMap, io}; | ||||||
|  |  | ||||||
| use super::Application; | use super::Application; | ||||||
| use futures_util::lock::Mutex; |  | ||||||
| use lazy_static::lazy_static; | use lazy_static::lazy_static; | ||||||
| use tokio::{ | use tokio::{ | ||||||
|     io::{AsyncBufReadExt, BufReader}, |     io::{AsyncBufReadExt, BufReader}, | ||||||
|     process::{Child, Command}, |     process::{Child, Command}, | ||||||
| }; | }; | ||||||
|  | use tokio::sync::Mutex; | ||||||
|  |  | ||||||
| lazy_static! { | lazy_static! { | ||||||
|     static ref STDOUT: Mutex<HashMap<String, String>> = Mutex::new(HashMap::new()); |     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