diff --git a/docs/docs/gurt-protocol.md b/docs/docs/gurt-protocol.md index 8c2942b..0604a79 100644 --- a/docs/docs/gurt-protocol.md +++ b/docs/docs/gurt-protocol.md @@ -141,28 +141,20 @@ All connections must use TLS 1.3 for encryption. This means you have to generate ### Setup for Production -For production deployments, you'll need to generate your own certificates since traditional Certificate Authorities don't support custom protocols: +For production deployments, you'll need to install GurtCA from the Github repository for Gurted, and use it to request certificates for your domain. -1. **Generate production certificates with OpenSSL:** +1. **Generate production certificates with GurtCA:** ```bash - # Generate private key - openssl genpkey -algorithm RSA -out gurt-server.key -pkcs8 -v - - # Generate certificate signing request - openssl req -new -key gurt-server.key -out gurt-server.csr - - # Generate self-signed certificate (valid for 365 days) - openssl x509 -req -days 365 -in gurt-server.csr -signkey gurt-server.key -out gurt-server.crt - - # Or generate both key and certificate in one step - openssl req -x509 -newkey rsa:4096 -keyout gurt-server.key -out gurt-server.crt -days 365 -nodes + gurtca request yourdomain.real --output ./certs ``` 2. **Deploy with production certificates:** ```bash - cargo run --release serve --cert gurt-server.crt --key gurt-server.key --host 0.0.0.0 --port 4878 + cargo run --release serve --cert ./certs/yourdomain.real.crt --key ./certs/yourdomain.real.key --host 0.0.0.0 --port 4878 ``` +Be careful, your `.key` file is the private key, do not share it with anyone! + ### Development Environment Setup To set up a development environment for GURT, follow these steps: diff --git a/docs/docs/lua/download.md b/docs/docs/lua/download.md new file mode 100644 index 0000000..6fa1d2c --- /dev/null +++ b/docs/docs/lua/download.md @@ -0,0 +1,22 @@ +# Download API + +The download API allows Lua scripts to trigger file downloads from URLs. + +## gurt.download + +Downloads a file from a URL and saves it to the user's default download location. + +### Syntax + +```lua +download_id = gurt.download(url filename) +``` + +### Parameters + +- **url** (string): The URL to download from. Supports HTTP, HTTPS, and gurt:// protocols. +- **filename** (string, optional): The filename to save as. If not provided, the filename will be extracted from the URL or default to "download". + +### Returns + +- **download_id** (string): A unique identifier for the download operation. diff --git a/docs/sidebars.ts b/docs/sidebars.ts index a899cb9..648703c 100644 --- a/docs/sidebars.ts +++ b/docs/sidebars.ts @@ -48,6 +48,7 @@ const sidebars: SidebarsConfig = { 'lua/crumbs', 'lua/audio', 'lua/canvas', + 'lua/download', 'lua/network', 'lua/regex', 'lua/handling', diff --git a/docs/static/img/docs/crt.gif b/docs/static/img/docs/crt.gif index 1fe9291..2d32b3d 100644 Binary files a/docs/static/img/docs/crt.gif and b/docs/static/img/docs/crt.gif differ diff --git a/docs/static/img/docs/download.gif b/docs/static/img/docs/download.gif new file mode 100644 index 0000000..5e88b77 Binary files /dev/null and b/docs/static/img/docs/download.gif differ diff --git a/flumi/Scenes/DevTools.tscn b/flumi/Scenes/DevTools.tscn index ace2907..fb2401e 100644 --- a/flumi/Scenes/DevTools.tscn +++ b/flumi/Scenes/DevTools.tscn @@ -489,6 +489,11 @@ visible = false layout_mode = 2 metadata/_tab_index = 2 +[node name="Messages" type="VBoxContainer" parent="DevTools/TabContainer/Network/MainContainer/RightPanel/PanelContainer/HBoxContainer/TabContainer"] +visible = false +layout_mode = 2 +metadata/_tab_index = 3 + [node name="Application" type="Label" parent="DevTools/TabContainer"] visible = false layout_mode = 2 diff --git a/flumi/Scenes/main.tscn b/flumi/Scenes/main.tscn index 1f82322..38f5db4 100644 --- a/flumi/Scenes/main.tscn +++ b/flumi/Scenes/main.tscn @@ -122,6 +122,7 @@ layout_mode = 2 [node name="Popup" type="Button" parent="VBoxContainer/TabContainer"] custom_minimum_size = Vector2(50, 0) layout_mode = 2 +focus_mode = 1 theme_override_styles/focus = SubResource("StyleBoxEmpty_344ge") theme_override_styles/hover = SubResource("StyleBoxFlat_hptm8") theme_override_styles/pressed = SubResource("StyleBoxFlat_6bp64") @@ -138,6 +139,7 @@ layout_mode = 2 [node name="NewTabButton" type="Button" parent="VBoxContainer/TabContainer"] custom_minimum_size = Vector2(50, 0) layout_mode = 2 +focus_mode = 1 theme_override_styles/focus = SubResource("StyleBoxEmpty_344ge") theme_override_styles/hover = SubResource("StyleBoxFlat_ynf5e") theme_override_styles/pressed = SubResource("StyleBoxFlat_6bp64") @@ -165,6 +167,7 @@ layout_mode = 2 [node name="BackButton" type="Button" parent="VBoxContainer/HBoxContainer"] custom_minimum_size = Vector2(45, 0) layout_mode = 2 +focus_mode = 1 theme_override_styles/focus = SubResource("StyleBoxEmpty_d1ilt") theme_override_styles/hover = SubResource("StyleBoxFlat_fdnlq") theme_override_styles/pressed = SubResource("StyleBoxFlat_d1ilt") @@ -175,6 +178,7 @@ icon_alignment = 1 [node name="ForwardButton" type="Button" parent="VBoxContainer/HBoxContainer"] custom_minimum_size = Vector2(45, 0) layout_mode = 2 +focus_mode = 1 theme_override_styles/focus = SubResource("StyleBoxEmpty_d1ilt") theme_override_styles/hover = SubResource("StyleBoxFlat_fdnlq") theme_override_styles/pressed = SubResource("StyleBoxFlat_d1ilt") @@ -185,6 +189,7 @@ icon_alignment = 1 [node name="RefreshButton" type="Button" parent="VBoxContainer/HBoxContainer"] custom_minimum_size = Vector2(45, 0) layout_mode = 2 +focus_mode = 1 theme_override_styles/focus = SubResource("StyleBoxEmpty_d1ilt") theme_override_styles/hover = SubResource("StyleBoxFlat_fdnlq") theme_override_styles/pressed = SubResource("StyleBoxFlat_d1ilt") @@ -216,6 +221,7 @@ stretch_mode = 5 [node name="OptionsButton" type="Button" parent="VBoxContainer/HBoxContainer"] custom_minimum_size = Vector2(45, 0) layout_mode = 2 +focus_mode = 1 theme_override_styles/focus = SubResource("StyleBoxEmpty_d1ilt") theme_override_styles/hover = SubResource("StyleBoxFlat_fdnlq") theme_override_styles/pressed = SubResource("StyleBoxFlat_d1ilt") diff --git a/flumi/Scripts/Browser/DownloadManager.gd b/flumi/Scripts/Browser/DownloadManager.gd index 52c8802..f56e338 100644 --- a/flumi/Scripts/Browser/DownloadManager.gd +++ b/flumi/Scripts/Browser/DownloadManager.gd @@ -72,13 +72,8 @@ func _start_download(download_id: String, url: String, save_path: String, downlo progress_ui.setup_download(download_id, download_data) progress_ui.download_cancelled.connect(_on_download_progress_cancelled) - - var http_request = HTTPRequest.new() - http_request.name = "DownloadRequest_" + download_id - main_node.add_child(http_request) - + active_downloads[download_id] = { - "http_request": http_request, "save_path": save_path, "progress_ui": progress_ui, "start_time": Time.get_ticks_msec() / 1000.0, @@ -88,25 +83,43 @@ func _start_download(download_id: String, url: String, save_path: String, downlo "filename": download_data.get("filename", ""), "current_site": download_data.get("current_site", "") } - + + if url.begins_with("gurt://"): + _download_gurt_resource(download_id, url) + else: + _start_http_download(download_id, url) + +func _start_http_download(download_id: String, url: String): + var http_request = HTTPRequest.new() + http_request.name = "DownloadRequest_" + download_id + main_node.add_child(http_request) + + if not active_downloads.has(download_id): + http_request.queue_free() + return + + active_downloads[download_id]["http_request"] = http_request + + var save_path = active_downloads[download_id]["save_path"] http_request.set_download_file(save_path) - + http_request.request_completed.connect(func(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray): _on_download_completed(download_id, result, response_code, headers, body) ) - + var headers = ["User-Agent: GURT Browser 1.0"] var request_error = http_request.request(url, headers) - + if request_error != OK: var error_msg = "Failed to start download: " + str(request_error) print(error_msg) + var progress_ui = active_downloads[download_id]["progress_ui"] if progress_ui: progress_ui.set_error(error_msg) http_request.queue_free() active_downloads.erase(download_id) return - + var timer = Timer.new() timer.name = "ProgressTimer_" + download_id timer.wait_time = 0.5 @@ -114,12 +127,59 @@ func _start_download(download_id: String, url: String, save_path: String, downlo main_node.add_child(timer) timer.start() +func _download_gurt_resource(download_id: String, url: String): + if not active_downloads.has(download_id): + return + + var progress_ui = active_downloads[download_id]["progress_ui"] + var save_path = active_downloads[download_id]["save_path"] + + if progress_ui: + progress_ui.update_progress(0, 0, -1) # -1 indicates unknown total size + + var resource_data = await Network.fetch_gurt_resource(url, true) + + if not active_downloads.has(download_id): + return + + if resource_data.is_empty(): + var error_msg = "Failed to fetch gurt:// resource" + print(error_msg) + if progress_ui: + progress_ui.set_error(error_msg) + active_downloads.erase(download_id) + return + + var file = FileAccess.open(save_path, FileAccess.WRITE) + if not file: + var error_msg = "Failed to create download file: " + save_path + print(error_msg) + if progress_ui: + progress_ui.set_error(error_msg) + active_downloads.erase(download_id) + return + + file.store_buffer(resource_data) + file.close() + + var file_size = resource_data.size() + + active_downloads[download_id]["total_bytes"] = file_size + active_downloads[download_id]["downloaded_bytes"] = file_size + + if progress_ui: + progress_ui.set_completed(save_path) + + _add_to_download_history(active_downloads[download_id], file_size, save_path) + + active_downloads.erase(download_id) + func _update_download_progress(download_id: String): if not active_downloads.has(download_id): return var download_info = active_downloads[download_id] - var http_request = download_info.http_request + var http_request = download_info.get("http_request", null) var progress_ui = download_info.progress_ui if http_request and progress_ui: @@ -180,11 +240,12 @@ func _on_download_progress_cancelled(download_id: String): return var download_info = active_downloads[download_id] - - if download_info.http_request: - download_info.http_request.cancel_request() - download_info.http_request.queue_free() - + + var http_request = download_info.get("http_request", null) + if http_request: + http_request.cancel_request() + http_request.queue_free() + var timer = main_node.get_node_or_null("ProgressTimer_" + download_id) if timer: timer.queue_free() diff --git a/flumi/Scripts/Browser/NetworkManager.gd b/flumi/Scripts/Browser/NetworkManager.gd index bc970b0..4316012 100644 --- a/flumi/Scripts/Browser/NetworkManager.gd +++ b/flumi/Scripts/Browser/NetworkManager.gd @@ -138,4 +138,54 @@ func add_completed_request(url: String, method: String, is_from_lua: bool, statu all_requests.append(request) if dev_tools_network_tab: - dev_tools_network_tab.add_network_request(request) \ No newline at end of file + dev_tools_network_tab.add_network_request(request) + +func start_websocket_connection(url: String, websocket_id: String) -> NetworkRequest: + var request = NetworkRequest.create_websocket_connection(url, websocket_id) + active_requests[request.id] = request + all_requests.append(request) + + if dev_tools_network_tab: + dev_tools_network_tab.add_network_request(request) + + request_started.emit(request) + return request + +func add_websocket_message(url: String, websocket_id: String, direction: String, message: String): + var connection_request: NetworkRequest = null + + for request in active_requests.values(): + if request.websocket_id == websocket_id and request.websocket_event_type == "connection": + connection_request = request + break + + if not connection_request: + for request in all_requests: + if request.websocket_id == websocket_id and request.websocket_event_type == "connection": + connection_request = request + break + + if connection_request: + connection_request.add_websocket_message(direction, message) + + if dev_tools_network_tab: + dev_tools_network_tab.update_request_item(connection_request) + + request_completed.emit(connection_request) + +func update_websocket_connection(websocket_id: String, status: String, status_code: int = 200, status_text: String = "OK"): + for request in active_requests.values(): + if request.websocket_id == websocket_id and request.websocket_event_type == "connection": + request.update_websocket_status(status, status_code, status_text) + + if status in ["closed", "error"]: + active_requests.erase(request.id) + + if dev_tools_network_tab: + dev_tools_network_tab.update_request_item(request) + + if request.status == NetworkRequest.RequestStatus.SUCCESS: + request_completed.emit(request) + else: + request_failed.emit(request) + break \ No newline at end of file diff --git a/flumi/Scripts/Browser/NetworkRequest.gd b/flumi/Scripts/Browser/NetworkRequest.gd index 571b303..73f0125 100644 --- a/flumi/Scripts/Browser/NetworkRequest.gd +++ b/flumi/Scripts/Browser/NetworkRequest.gd @@ -42,6 +42,31 @@ var response_body_bytes: PackedByteArray = [] var mime_type: String = "" var is_from_lua: bool = false +var websocket_id: String = "" +var websocket_event_type: String = "" # "connection", "close", "error" +var connection_status: String = "" # "connecting", "open", "closing", "closed" +var websocket_messages: Array[WebSocketMessage] = [] + +class WebSocketMessage: + var hour: int + var minute: int + var second: int + var direction: String # "sent" or "received" + var content: String + var size: int + + func _init(dir: String, msg: String): + var local_time = Time.get_datetime_dict_from_system(false) + hour = local_time.hour + minute = local_time.minute + second = local_time.second + direction = dir + content = msg + size = msg.length() + + func get_formatted_time() -> String: + return "%02d:%02d:%02d" % [hour, minute, second] + func _init(request_url: String = "", request_method: String = "GET"): id = generate_id() url = request_url @@ -62,6 +87,16 @@ func generate_id() -> String: func extract_name_from_url(request_url: String) -> String: if request_url.is_empty(): return "Unknown" + + if request_url.begins_with("ws://") or request_url.begins_with("wss://"): + if not websocket_event_type.is_empty(): + match websocket_event_type: + "connection": + return "WebSocket" + "close": + return "WebSocket Close" + "error": + return "WebSocket Error" var parts = request_url.split("/") if parts.size() > 0: @@ -177,7 +212,7 @@ static func format_bytes(given_size: int) -> String: elif given_size < 1024 * 1024: return str(given_size / 1024) + " KB" else: - return str(given_size / (1024 * 1024)) + " MB" + return str(given_size / (1024.0 * 1024)) + " MB" func get_time_display() -> String: if status == RequestStatus.PENDING: @@ -205,3 +240,40 @@ func get_icon_texture() -> Texture2D: return load("res://Assets/Icons/arrow-down-up.svg") _: return load("res://Assets/Icons/search.svg") + +static func create_websocket_connection(ws_url: String, ws_id: String) -> NetworkRequest: + var request = NetworkRequest.new(ws_url, "WS") + request.type = RequestType.SOCKET + request.websocket_id = ws_id + request.websocket_event_type = "connection" + request.connection_status = "connecting" + request.is_from_lua = true + return request + +func add_websocket_message(direction: String, message: String): + var ws_message = WebSocketMessage.new(direction, message) + websocket_messages.append(ws_message) + + var total_message_size = 0 + for msg in websocket_messages: + total_message_size += msg.size + size = total_message_size + +func update_websocket_status(new_status: String, status_code: int = 200, status_text: String = "OK"): + connection_status = new_status + self.status_code = status_code + self.status_text = status_text + + match new_status: + "open": + status = RequestStatus.SUCCESS + "closed": + if status_code >= 1000 and status_code < 1100: + status = RequestStatus.SUCCESS + else: + status = RequestStatus.ERROR + "error": + status = RequestStatus.ERROR + + end_time = Time.get_ticks_msec() + time_ms = end_time - start_time diff --git a/flumi/Scripts/Browser/NetworkTab.gd b/flumi/Scripts/Browser/NetworkTab.gd index bc15d84..00fb8e9 100644 --- a/flumi/Scripts/Browser/NetworkTab.gd +++ b/flumi/Scripts/Browser/NetworkTab.gd @@ -11,6 +11,7 @@ const NetworkRequestItemScene = preload("res://Scenes/NetworkRequestItem.tscn") @onready var headers_tab: VBoxContainer = $MainContainer/RightPanel/PanelContainer/HBoxContainer/TabContainer/Headers @onready var preview_tab: Control = $MainContainer/RightPanel/PanelContainer/HBoxContainer/TabContainer/Preview @onready var response_tab: Control = $MainContainer/RightPanel/PanelContainer/HBoxContainer/TabContainer/Response +@onready var messages_tab: Control = $MainContainer/RightPanel/PanelContainer/HBoxContainer/TabContainer/Messages # Header components @onready var status_header: Label = %StatusHeader @@ -31,7 +32,7 @@ const NetworkRequestItemScene = preload("res://Scenes/NetworkRequestItem.tscn") @onready var syntax_highlighter = preload("res://Resources/LuaSyntaxHighlighter.tres") var network_requests: Array[NetworkRequest] = [] -var current_filter: NetworkRequest.RequestType = -1 # -1 means all +var current_filter: int = -1 # -1 means all, otherwise NetworkRequest.RequestType var selected_request: NetworkRequest = null var request_items: Dictionary = {} @@ -66,7 +67,7 @@ func apply_filter(): for request in network_requests: var item = request_items.get(request.id) if item: - var should_show = (current_filter == -1) or (request.type == current_filter) + var should_show = (current_filter == -1) or (int(request.type) == current_filter) item.visible = should_show func update_request_item(request: NetworkRequest): @@ -76,6 +77,9 @@ func update_request_item(request: NetworkRequest): request_item.update_display() + if selected_request == request and details_panel.visible: + update_details_panel(request) + apply_filter() update_status_bar() @@ -84,11 +88,19 @@ func update_details_panel(request: NetworkRequest): update_headers_tab(request) update_preview_tab(request) update_response_tab(request) + update_messages_tab(request) + + if request.type == NetworkRequest.RequestType.SOCKET: + messages_tab.visible = true + details_tab_container.set_tab_title(3, "Messages (" + str(request.websocket_messages.size()) + ")") + else: + messages_tab.visible = false func clear_details_panel(): for child in headers_tab.get_children(): child.queue_free() for child in preview_tab.get_children(): child.queue_free() for child in response_tab.get_children(): child.queue_free() + for child in messages_tab.get_children(): child.queue_free() func create_collapsible_section(title: String, expanded: bool = false) -> VBoxContainer: var section = VBoxContainer.new() @@ -154,6 +166,17 @@ func update_headers_tab(request: NetworkRequest): add_header_row(general_content, "Request Method:", request.method) add_header_row(general_content, "Status Code:", str(request.status_code) + " " + request.status_text) + # WebSocket information + if request.type == NetworkRequest.RequestType.SOCKET: + var ws_section = create_collapsible_section("WebSocket Information", true) + headers_tab.add_child(ws_section) + + var ws_content = ws_section.get_child(1) + add_header_row(ws_content, "WebSocket ID:", request.websocket_id) + add_header_row(ws_content, "Event Type:", request.websocket_event_type) + add_header_row(ws_content, "Connection Status:", request.connection_status) + add_header_row(ws_content, "Total Messages:", str(request.websocket_messages.size())) + # Request Headers section if not request.request_headers.is_empty(): var request_headers_section = create_collapsible_section("Request Headers", false) @@ -173,6 +196,32 @@ func update_headers_tab(request: NetworkRequest): add_header_row(response_headers_content, header_name + ":", str(request.response_headers[header_name])) func update_preview_tab(request: NetworkRequest): + if request.type == NetworkRequest.RequestType.SOCKET: + var content_to_show = "" + + match request.websocket_event_type: + "connection": + content_to_show = "WebSocket connection event\n" + content_to_show += "URL: " + request.url + "\n" + content_to_show += "Status: " + request.connection_status + "\n" + if request.status_code > 0: + content_to_show += "Status Code: " + str(request.status_code) + " " + request.status_text + "\n" + content_to_show += "Messages exchanged: " + str(request.websocket_messages.size()) + "close", "error": + content_to_show = "WebSocket " + request.websocket_event_type + " event\n" + content_to_show += "Status Code: " + str(request.status_code) + "\n" + content_to_show += "Reason: " + request.status_text + + if not content_to_show.is_empty(): + var code_edit = CodeEditUtils.create_code_edit({ + "text": content_to_show, + "editable": false, + "show_line_numbers": false, + "syntax_highlighter": null + }) + preview_tab.add_child(code_edit) + return + # For images, show the image in the preview tab if request.type == NetworkRequest.RequestType.IMG and request.status == NetworkRequest.RequestStatus.SUCCESS: var image = Image.new() @@ -240,6 +289,42 @@ func update_preview_tab(request: NetworkRequest): preview_tab.add_child(code_edit) func update_response_tab(request: NetworkRequest): + if request.type == NetworkRequest.RequestType.SOCKET: + var content_to_show = "" + + match request.websocket_event_type: + "connection": + content_to_show = "WebSocket Connection Details\n\n" + content_to_show += "This is a WebSocket connection request.\n" + content_to_show += "Connection Status: " + request.connection_status + "\n" + content_to_show += "WebSocket ID: " + request.websocket_id + "\n" + content_to_show += "Total Messages: " + str(request.websocket_messages.size()) + "\n" + if request.status_code > 0: + content_to_show += "Status Code: " + str(request.status_code) + " " + request.status_text + "\n" + content_to_show += "\nNote: Individual messages can be viewed in the 'Messages' tab." + "close", "error": + content_to_show = "WebSocket " + request.websocket_event_type.capitalize() + " Event\n\n" + content_to_show += "Status Code: " + str(request.status_code) + "\n" + content_to_show += "Reason: " + request.status_text + "\n" + content_to_show += "WebSocket ID: " + request.websocket_id + "\n" + content_to_show += "Total Messages Exchanged: " + str(request.websocket_messages.size()) + + if not content_to_show.is_empty(): + var code_edit = CodeEditUtils.create_code_edit({ + "text": content_to_show, + "editable": false, + "show_line_numbers": false, + "syntax_highlighter": null + }) + response_tab.add_child(code_edit) + else: + var label = Label.new() + label.text = "No WebSocket data to display" + label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER + response_tab.add_child(label) + return + if request.type == NetworkRequest.RequestType.IMG: var label = Label.new() label.text = "This response contains image data. See the \"Preview\" tab to view the image." @@ -280,6 +365,165 @@ func update_response_tab(request: NetworkRequest): response_tab.add_child(code_edit) +func update_messages_tab(request: NetworkRequest): + if request.type != NetworkRequest.RequestType.SOCKET: + return + + if request.websocket_messages.is_empty(): + var label = Label.new() + label.text = "No WebSocket messages yet" + label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER + messages_tab.add_child(label) + return + + var scroll_container = ScrollContainer.new() + scroll_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL + scroll_container.size_flags_vertical = Control.SIZE_EXPAND_FILL + + var messages_container = VBoxContainer.new() + messages_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL + scroll_container.add_child(messages_container) + + var header_container = VBoxContainer.new() + header_container.add_theme_constant_override("separation", 8) + + var search_container = HBoxContainer.new() + search_container.add_theme_constant_override("separation", 8) + + var search_input = LineEdit.new() + search_input.placeholder_text = "Filter" + search_input.size_flags_horizontal = Control.SIZE_EXPAND_FILL + + var focus_style = StyleBoxFlat.new() + focus_style.content_margin_left = 16.0 + focus_style.content_margin_right = 8.0 + focus_style.bg_color = Color(0.168627, 0.168627, 0.168627, 1) + focus_style.border_width_left = 1 + focus_style.border_width_top = 1 + focus_style.border_width_right = 1 + focus_style.border_width_bottom = 1 + focus_style.border_color = Color(0.247059, 0.466667, 0.807843, 1) + focus_style.corner_radius_top_left = 15 + focus_style.corner_radius_top_right = 15 + focus_style.corner_radius_bottom_right = 15 + focus_style.corner_radius_bottom_left = 15 + search_input.add_theme_stylebox_override("focus", focus_style) + + var normal_style = StyleBoxFlat.new() + normal_style.content_margin_left = 16.0 + normal_style.content_margin_right = 8.0 + normal_style.bg_color = Color(0.168627, 0.168627, 0.168627, 1) + normal_style.corner_radius_top_left = 15 + normal_style.corner_radius_top_right = 15 + normal_style.corner_radius_bottom_right = 15 + normal_style.corner_radius_bottom_left = 15 + search_input.add_theme_stylebox_override("normal", normal_style) + + search_container.add_child(search_input) + + header_container.add_child(search_container) + + var spacer = Control.new() + spacer.custom_minimum_size.y = 8 + header_container.add_child(spacer) + + messages_container.add_child(header_container) + + var message_rows: Array[Control] = [] + var search_term = "" + + var update_search = func(): + var filter_text = search_input.text.to_lower() + + for row_index in range(message_rows.size()): + var row = message_rows[row_index] + var message = request.websocket_messages[row_index] + var should_show = filter_text.is_empty() or message.content.to_lower().contains(filter_text) + row.visible = should_show + + search_input.text_changed.connect(func(_text): update_search.call()) + + for i in range(request.websocket_messages.size()): + var message = request.websocket_messages[i] + + var message_panel = PanelContainer.new() + message_panel.custom_minimum_size.y = 32 + + var button = Button.new() + button.flat = true + button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND + button.focus_mode = Control.FOCUS_NONE + + button.anchors_preset = Control.PRESET_FULL_RECT + button.size_flags_horizontal = Control.SIZE_EXPAND_FILL + button.size_flags_vertical = Control.SIZE_EXPAND_FILL + + var panel_style = StyleBoxFlat.new() + if message.direction == "sent": + panel_style.bg_color = Color(0.2, 0.3, 0.5, 0.3) + else: + panel_style.bg_color = Color(0.0, 0.0, 0.0, 0.0) + + panel_style.content_margin_left = 6 + panel_style.content_margin_right = 6 + panel_style.content_margin_top = 2 + panel_style.content_margin_bottom = 2 + message_panel.add_theme_stylebox_override("panel", panel_style) + + var message_row = HBoxContainer.new() + message_row.add_theme_constant_override("separation", 8) + + var direction_label = Label.new() + var direction_icon = "↑" if message.direction == "sent" else "↓" + var direction_color = Color(0.7, 0.9, 1.0) if message.direction == "sent" else Color(1.0, 0.7, 0.7) + + direction_label.text = direction_icon + direction_label.add_theme_font_size_override("font_size", 16) + direction_label.add_theme_color_override("font_color", direction_color) + direction_label.custom_minimum_size.x = 16 + direction_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + message_row.add_child(direction_label) + + var timestamp_label = Label.new() + timestamp_label.text = message.get_formatted_time() + timestamp_label.add_theme_font_size_override("font_size", 14) + timestamp_label.add_theme_color_override("font_color", Color(0.6, 0.6, 0.6, 1.0)) + timestamp_label.custom_minimum_size.x = 80 + message_row.add_child(timestamp_label) + + var content_label = Label.new() + var content_text = message.content + if content_text.length() > 60: + content_text = content_text.substr(0, 57) + "..." + content_text = content_text.replace("\n", " ").replace("\r", " ") + + content_label.text = content_text + content_label.add_theme_font_size_override("font_size", 16) + content_label.add_theme_color_override("font_color", Color(1.0, 1.0, 1.0, 1.0)) + content_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL + content_label.clip_contents = true + content_label.autowrap_mode = TextServer.AUTOWRAP_OFF + message_row.add_child(content_label) + + var size_label = Label.new() + size_label.text = str(message.size) + "b" + size_label.add_theme_font_size_override("font_size", 14) + size_label.add_theme_color_override("font_color", Color(0.6, 0.6, 0.6, 1.0)) + size_label.custom_minimum_size.x = 40 + size_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT + message_row.add_child(size_label) + + button.pressed.connect(func(): DisplayServer.clipboard_set(message.content)) + + message_panel.add_child(message_row) + message_panel.add_child(button) + + messages_container.add_child(message_panel) + message_rows.append(message_panel) + + messages_tab.add_child(scroll_container) + func update_status_bar(): var total_requests = network_requests.size() var total_size = 0 diff --git a/flumi/Scripts/Browser/Tab.gd b/flumi/Scripts/Browser/Tab.gd index 9fd22db..e454201 100644 --- a/flumi/Scripts/Browser/Tab.gd +++ b/flumi/Scripts/Browser/Tab.gd @@ -60,6 +60,25 @@ func _process(_delta): else: close_button.add_theme_stylebox_override("normal", CLOSE_BUTTON_NORMAL) +func _input(event): + # make sure we are hovering over the tab + if mouse_over_tab and event.is_action_pressed("CloseTabMouse"): + var close_tween = create_tween() + close_tween.set_ease(Tween.EASE_IN) + close_tween.set_trans(Tween.TRANS_CUBIC) + + animate_tab_close(close_tween) + + await close_tween.finished + tab_closed.emit() + queue_free() + +func animate_tab_close(tween: Tween) -> void: + tween.parallel().tween_property(self, "custom_minimum_size:x", 0.0, 0.15) + tween.parallel().tween_property(self, "size:x", 0.0, 0.15) + tween.parallel().tween_property(button, "custom_minimum_size:x", 0.0, 0.15) + tween.parallel().tween_property(button, "size:x", 0.0, 0.15) + func set_title(title: String) -> void: button.text = title button.set_meta("original_text", title) @@ -231,10 +250,7 @@ func _on_close_button_pressed() -> void: close_tween.set_ease(Tween.EASE_IN) close_tween.set_trans(Tween.TRANS_CUBIC) - close_tween.parallel().tween_property(self, "custom_minimum_size:x", 0.0, 0.15) - close_tween.parallel().tween_property(self, "size:x", 0.0, 0.15) - close_tween.parallel().tween_property(button, "custom_minimum_size:x", 0.0, 0.15) - close_tween.parallel().tween_property(button, "size:x", 0.0, 0.15) + animate_tab_close(close_tween) await close_tween.finished tab_closed.emit() diff --git a/flumi/Scripts/ClientPool.gd b/flumi/Scripts/ClientPool.gd new file mode 100644 index 0000000..bd4953c --- /dev/null +++ b/flumi/Scripts/ClientPool.gd @@ -0,0 +1,48 @@ +class_name ClientPool +extends RefCounted + +static var gurt_clients: Dictionary = {} +static var client_last_used: Dictionary = {} +static var client_timeout_ms: int = 30000 + +static func _cleanup_idle_clients(): + var current_time = Time.get_ticks_msec() + var to_remove = [] + + for domain in client_last_used: + if current_time - client_last_used[domain] > client_timeout_ms: + to_remove.append(domain) + + for domain in to_remove: + if gurt_clients.has(domain): + gurt_clients[domain].disconnect() + gurt_clients.erase(domain) + client_last_used.erase(domain) + +static func get_or_create_gurt_client(domain: String) -> GurtProtocolClient: + _cleanup_idle_clients() + + if gurt_clients.has(domain): + client_last_used[domain] = Time.get_ticks_msec() + return gurt_clients[domain] + + var client = GurtProtocolClient.new() + + for ca_cert in CertificateManager.trusted_ca_certificates: + client.add_ca_certificate(ca_cert) + + if not client.create_client_with_dns(30, GurtProtocol.DNS_SERVER_IP, GurtProtocol.DNS_SERVER_PORT): + return null + + gurt_clients[domain] = client + client_last_used[domain] = Time.get_ticks_msec() + return client + +static func extract_domain_from_url(gurt_url: String) -> String: + var host_domain = gurt_url + if host_domain.begins_with("gurt://"): + host_domain = host_domain.right(-7) + var slash_pos = host_domain.find("/") + if slash_pos != -1: + host_domain = host_domain.left(slash_pos) + return host_domain \ No newline at end of file diff --git a/flumi/Scripts/ClientPool.gd.uid b/flumi/Scripts/ClientPool.gd.uid new file mode 100644 index 0000000..d617e72 --- /dev/null +++ b/flumi/Scripts/ClientPool.gd.uid @@ -0,0 +1 @@ +uid://dkortg18h0wa2 diff --git a/flumi/Scripts/Network.gd b/flumi/Scripts/Network.gd index b26c51b..d01ea38 100644 --- a/flumi/Scripts/Network.gd +++ b/flumi/Scripts/Network.gd @@ -1,5 +1,14 @@ extends Node +const ClientPool = preload("res://Scripts/ClientPool.gd") + +func _ready(): + var timer = Timer.new() + timer.wait_time = 10.0 + timer.autostart = true + timer.timeout.connect(ClientPool._cleanup_idle_clients) + add_child(timer) + func fetch_image(url: String) -> ImageTexture: if url.is_empty(): return null @@ -170,27 +179,17 @@ func fetch_gurt_resource(url: String, as_binary: bool = false): var network_request = NetworkManager.start_request(gurt_url, "GET", false) - var client = GurtProtocolClient.new() - - for ca_cert in CertificateManager.trusted_ca_certificates: - client.add_ca_certificate(ca_cert) - - if not client.create_client_with_dns(30, GurtProtocol.DNS_SERVER_IP, GurtProtocol.DNS_SERVER_PORT): + var host_domain = ClientPool.extract_domain_from_url(gurt_url) + + var client = ClientPool.get_or_create_gurt_client(host_domain) + if client == null: NetworkManager.fail_request(network_request.id, "Failed to create GURT client") - return "" - - var host_domain = gurt_url - if host_domain.begins_with("gurt://"): - host_domain = host_domain.substr(7) - var slash_pos = host_domain.find("/") - if slash_pos != -1: - host_domain = host_domain.substr(0, slash_pos) + return PackedByteArray() if as_binary else "" var response = client.request(gurt_url, { "method": "GET", "headers": {"Host": host_domain} }) - client.disconnect() if not response or not response.is_success: var error_msg = "Failed to load GURT resource" diff --git a/flumi/Scripts/Utils/Lua/WebSocket.gd b/flumi/Scripts/Utils/Lua/WebSocket.gd index 71dfc83..0a1f7ff 100644 --- a/flumi/Scripts/Utils/Lua/WebSocket.gd +++ b/flumi/Scripts/Utils/Lua/WebSocket.gd @@ -23,6 +23,8 @@ class WebSocketWrapper: if connection_status: return + NetworkManager.call_deferred("start_websocket_connection", url, instance_id) + var error = websocket.connect_to_url(url) if error == OK: @@ -42,8 +44,10 @@ class WebSocketWrapper: timer.call_deferred("start") else: trigger_event("error", {"message": "No scene available for WebSocket timer"}) + NetworkManager.call_deferred("update_websocket_connection", instance_id, "error", 0, "No scene available") else: trigger_event("error", {"message": "Failed to connect to " + url + " (error: " + str(error) + ")"}) + NetworkManager.call_deferred("update_websocket_connection", instance_id, "error", 0, "Connection failed: " + str(error)) func _poll_websocket(): if not websocket: @@ -57,17 +61,25 @@ class WebSocketWrapper: if not connection_status: connection_status = true trigger_event("open", {}) + # Update NetworkManager with successful connection + NetworkManager.call_deferred("update_websocket_connection", instance_id, "open", 200, "Connected") # Check for messages while websocket.get_available_packet_count() > 0: var packet = websocket.get_packet() var message = packet.get_string_from_utf8() trigger_event("message", {"data": message}) + # Track received message in NetworkManager + NetworkManager.call_deferred("add_websocket_message", url, instance_id, "received", message) WebSocketPeer.STATE_CLOSED: if connection_status: connection_status = false trigger_event("close", {}) + # Update NetworkManager with closed connection + var close_code = websocket.get_close_code() + var close_reason = websocket.get_close_reason() + NetworkManager.call_deferred("update_websocket_connection", instance_id, "closed", close_code, close_reason) # Clean up timer if timer: @@ -82,25 +94,33 @@ class WebSocketWrapper: # Connection is closing if connection_status: connection_status = false + NetworkManager.call_deferred("update_websocket_connection", instance_id, "closing", 0, "Closing") _: # Unknown state or connection failed if connection_status: connection_status = false trigger_event("close", {}) + NetworkManager.call_deferred("update_websocket_connection", instance_id, "closed", 0, "Unexpected disconnection") elif not connection_status: # This might be a connection failure trigger_event("error", {"message": "Connection failed or was rejected by server"}) + NetworkManager.call_deferred("update_websocket_connection", instance_id, "error", 0, "Connection failed or rejected") func send_message(message: String): if connection_status and websocket: websocket.send_text(message) + # Track sent message in NetworkManager + NetworkManager.call_deferred("add_websocket_message", url, instance_id, "sent", message) func close_connection(): if websocket: websocket.close() connection_status = false + # Update NetworkManager with manual close + NetworkManager.call_deferred("update_websocket_connection", instance_id, "closed", 1000, "Manually closed") + if timer: timer.queue_free() timer = null diff --git a/flumi/Scripts/main.gd b/flumi/Scripts/main.gd index 0766ec1..79da534 100644 --- a/flumi/Scripts/main.gd +++ b/flumi/Scripts/main.gd @@ -1,6 +1,8 @@ class_name Main extends Control +const ClientPool = preload("res://Scripts/ClientPool.gd") + @onready var website_container: Control = %WebsiteContainer @onready var tab_container: TabManager = $VBoxContainer/TabContainer @onready var search_bar: LineEdit = $VBoxContainer/HBoxContainer/LineEdit @@ -75,6 +77,7 @@ func _ready(): call_deferred("update_navigation_buttons") call_deferred("_handle_startup_behavior") + func _input(_event: InputEvent) -> void: if Input.is_action_just_pressed("DevTools"): _toggle_dev_tools() @@ -151,19 +154,16 @@ func fetch_gurt_content_async(gurt_url: String, tab: Tab, original_url: String, func _perform_gurt_request_threaded(request_data: Dictionary) -> Dictionary: var gurt_url: String = request_data.gurt_url - var client = GurtProtocolClient.new() - for ca_cert in CertificateManager.trusted_ca_certificates: - client.add_ca_certificate(ca_cert) - - if not client.create_client_with_dns(30, GurtProtocol.DNS_SERVER_IP, GurtProtocol.DNS_SERVER_PORT): - client.disconnect() + var host_domain = ClientPool.extract_domain_from_url(gurt_url) + + var client = ClientPool.get_or_create_gurt_client(host_domain) + if client == null: return {"success": false, "error": "Failed to connect to GURT DNS server at " + GurtProtocol.DNS_SERVER_IP + ":" + str(GurtProtocol.DNS_SERVER_PORT)} var response = client.request(gurt_url, { "method": "GET" }) - client.disconnect() if not response or not response.is_success: var error_msg = "Connection failed" @@ -290,7 +290,7 @@ func _on_search_focus_exited() -> void: if not current_domain.is_empty(): var display_text = current_domain if display_text.begins_with("gurt://"): - display_text = display_text.substr(7) + display_text = display_text.right(-7) elif display_text.begins_with("file://"): display_text = URLUtils.file_url_to_path(display_text) search_bar.text = display_text @@ -489,7 +489,7 @@ func render_content(html_bytes: PackedByteArray) -> void: var base_url_for_scripts = current_domain var query_pos = base_url_for_scripts.find("?") if query_pos != -1: - base_url_for_scripts = base_url_for_scripts.substr(0, query_pos) + base_url_for_scripts = base_url_for_scripts.left(query_pos) await parser.process_external_scripts(lua_api, null, base_url_for_scripts) var postprocess_element = parser.process_postprocess() @@ -838,7 +838,7 @@ func update_search_bar_from_current_domain() -> void: if not search_bar.has_focus() and not current_domain.is_empty(): var display_text = current_domain if display_text.begins_with("gurt://"): - display_text = display_text.substr(7) + display_text = display_text.right(-7) elif display_text.begins_with("file://"): display_text = URLUtils.file_url_to_path(display_text) search_bar.text = display_text @@ -879,7 +879,7 @@ func add_to_history(url: String, tab: Tab, add_to_navigation: bool = true): var clean_url = url if clean_url.begins_with("gurt://"): - clean_url = clean_url.substr(7) + clean_url = clean_url.right(-7) BrowserHistory.add_entry(clean_url, title, icon_url) update_navigation_buttons() diff --git a/flumi/addons/gurt-protocol/bin/windows/gurt_godot.dll b/flumi/addons/gurt-protocol/bin/windows/gurt_godot.dll index efaaffc..a2c3d93 100644 Binary files a/flumi/addons/gurt-protocol/bin/windows/gurt_godot.dll and b/flumi/addons/gurt-protocol/bin/windows/gurt_godot.dll differ diff --git a/flumi/build-scripts/flumi-installer.iss b/flumi/build-scripts/flumi-installer.iss index 8262d52..a95514c 100644 --- a/flumi/build-scripts/flumi-installer.iss +++ b/flumi/build-scripts/flumi-installer.iss @@ -1,6 +1,6 @@ [Setup] AppName=Flumi -AppVersion=1.0.2 +AppVersion=1.0.3 AppPublisher=Outpoot AppPublisherURL=https://github.com/gurted/flumi AppSupportURL=https://github.com/gurted/flumi/issues diff --git a/flumi/project.godot b/flumi/project.godot index 6dd1a5c..f56dac5 100644 --- a/flumi/project.godot +++ b/flumi/project.godot @@ -11,7 +11,7 @@ config_version=5 [application] config/name="Flumi" -config/version="1.0.1" +config/version="1.0.3" run/main_scene="uid://bytm7bt2s4ak8" config/use_custom_user_dir=true config/features=PackedStringArray("4.4", "Forward Plus") @@ -85,3 +85,8 @@ ReloadPage={ "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":true,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":82,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) ] } +CloseTabMouse={ +"deadzone": 0.2, +"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":4,"position":Vector2(134, 20),"global_position":Vector2(143, 68),"factor":1.0,"button_index":3,"canceled":false,"pressed":true,"double_click":false,"script":null) +] +} diff --git a/protocol/gdextension/Cargo.toml b/protocol/gdextension/Cargo.toml index 845c9de..86e2d03 100644 --- a/protocol/gdextension/Cargo.toml +++ b/protocol/gdextension/Cargo.toml @@ -14,15 +14,15 @@ crate-type = ["cdylib"] [dependencies] gurtlib = { path = "../library" } -godot = "0.1" +godot = { version = "0.1", features = ["experimental-threads"] } tokio = { version = "1.0", features = ["rt"] } url = "2.5" serde_json = "1.0" [profile.release] -opt-level = "z" +opt-level = 3 lto = true codegen-units = 1 +strip = true panic = "abort" -strip = true \ No newline at end of file diff --git a/protocol/gdextension/build.sh b/protocol/gdextension/build.sh index 90f82ca..88b7b58 100644 --- a/protocol/gdextension/build.sh +++ b/protocol/gdextension/build.sh @@ -33,7 +33,7 @@ while [[ $# -gt 0 ]]; do echo "" echo "Options:" echo " -t, --target TARGET Build target (debug|release) [default: release]" - echo " -p, --platform PLATFORM Target platform (windows|linux|macos|current)" + echo " -p, --platform PLATFORM Target platform (windows|linux|macos|macos-intel|current)" echo " -h, --help Show this help message" echo "" exit 0 @@ -82,6 +82,10 @@ case $PLATFORM in LIB_NAME="libgurt_godot.so" ;; macos) + RUST_TARGET="aarch64-apple-darwin" + LIB_NAME="libgurt_godot.dylib" + ;; + macos-intel) RUST_TARGET="x86_64-apple-darwin" LIB_NAME="libgurt_godot.dylib" ;; diff --git a/search-engine/Dockerfile b/search-engine/Dockerfile new file mode 100644 index 0000000..f6e6992 --- /dev/null +++ b/search-engine/Dockerfile @@ -0,0 +1,48 @@ +FROM rustlang/rust:nightly-alpine AS builder + +RUN apk add --no-cache \ + musl-dev \ + openssl-dev \ + openssl-libs-static \ + pkgconfig \ + postgresql-dev + +ENV OPENSSL_STATIC=1 +ENV OPENSSL_DIR=/usr +ENV PKG_CONFIG_ALLOW_CROSS=1 + +WORKDIR /app + +COPY protocol/library ../protocol/library +COPY search-engine/Cargo.toml ./ +COPY search-engine/src ./src + +RUN cargo build --release + +FROM alpine:3.19 + +RUN apk add --no-cache \ + ca-certificates \ + postgresql-client \ + wget + +RUN addgroup -g 1001 -S appgroup && \ + adduser -u 1001 -S appuser -G appgroup + +WORKDIR /app + +COPY --from=builder /app/target/release/gurted-search-engine /app/gurted-search-engine +COPY search-engine/frontend ./frontend + +RUN mkdir -p /app/config /app/data /app/search_indexes /app/certs && \ + chown -R appuser:appgroup /app + +USER appuser + +EXPOSE 8081 + +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:8081/health || exit 1 + +# Default command - runs the server subcommand +CMD ["./gurted-search-engine", "--config", "/app/config/config.toml", "server"] \ No newline at end of file diff --git a/search-engine/build.sh b/search-engine/build.sh new file mode 100644 index 0000000..d55d24e --- /dev/null +++ b/search-engine/build.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +echo "Building Gurted Search Engine Server..." + +echo "Stopping existing container..." +docker compose down + +echo "Building Docker image..." +docker compose build --no-cache --parallel + +echo "Starting services..." +docker compose up -d + +echo "Container logs (press Ctrl+C to stop following):" +docker compose logs -f search-engine \ No newline at end of file diff --git a/search-engine/docker-compose.yml b/search-engine/docker-compose.yml new file mode 100644 index 0000000..817d9e8 --- /dev/null +++ b/search-engine/docker-compose.yml @@ -0,0 +1,14 @@ +services: + search-engine: + build: + context: .. + dockerfile: search-engine/Dockerfile + container_name: gurted-search-engine + network_mode: host + environment: + RUST_LOG: info + volumes: + - ./config.toml:/app/config/config.toml:ro + - ./search_indexes:/app/search_indexes + - ./certs:/app/certs:ro + restart: unless-stopped \ No newline at end of file diff --git a/search-engine/src/crawler.rs b/search-engine/src/crawler.rs index 09c6aa3..fc19e8b 100644 --- a/search-engine/src/crawler.rs +++ b/search-engine/src/crawler.rs @@ -158,7 +158,14 @@ impl DomainCrawler { } fn parse_clanker_txt(&self, content: &str, base_url: &str) -> Result> { - let user_agent = &self.config.search.crawler_user_agent; + Self::parse_clanker_rules( + content, + base_url, + &self.config.search.crawler_user_agent, + ) + } + + fn parse_clanker_rules(content: &str, base_url: &str, user_agent: &str) -> Result> { let mut disallow_all = false; let mut user_agent_matches = false; let mut allowed_urls = Vec::new(); @@ -169,26 +176,31 @@ impl DomainCrawler { continue; } - if let Some(user_agent_value) = line.to_lowercase().strip_prefix("user-agent:") { - let current_user_agent = user_agent_value.trim().to_string(); - user_agent_matches = current_user_agent == "*" || current_user_agent.eq_ignore_ascii_case(user_agent); + let (directive, value) = match line.split_once(':') { + Some((directive, value)) => (directive.trim().to_lowercase(), value.trim()), + None => continue, + }; + + if directive == "user-agent" { + user_agent_matches = + value == "*" || value.eq_ignore_ascii_case(user_agent); continue; } - if user_agent_matches { - if let Some(path_value) = line.to_lowercase().strip_prefix("disallow:") { - let path = path_value.trim(); - if path == "/" { - disallow_all = true; - break; - } - } else if let Some(path_value) = line.to_lowercase().strip_prefix("allow:") { - let path = path_value.trim(); - if !path.is_empty() { - let full_url = Self::normalize_url(format!("{}{}", base_url, path)); - debug!("Added allowed URL from clanker.txt: {}", full_url); - allowed_urls.push(full_url); - } + if !user_agent_matches { + continue; + } + + if directive == "disallow" { + if value == "/" { + disallow_all = true; + break; + } + } else if directive == "allow" { + if !value.is_empty() { + let full_url = Self::normalize_url(format!("{}{}", base_url, value)); + debug!("Added allowed URL from clanker.txt: {}", full_url); + allowed_urls.push(full_url); } } } @@ -719,4 +731,41 @@ impl CrawlStats { duration_seconds: 0, } } +} + +#[cfg(test)] +mod tests { + use super::DomainCrawler; + + #[test] + fn parse_clanker_rules_preserves_case_in_allowed_urls() { + let content = "User-agent: TestBot\nAllow: /getpage?l=Fri,12Sep2025000605_ZzesV.txt\n"; + let result = DomainCrawler::parse_clanker_rules(content, "gurt://wi.ki", "TestBot") + .expect("expected allow list"); + + assert_eq!( + result, + vec!["gurt://wi.ki/getpage?l=Fri,12Sep2025000605_ZzesV.txt".to_string()] + ); + } + + #[test] + fn parse_clanker_rules_handles_case_insensitive_directives() { + let content = "user-Agent: AnotherBot\nAlLoW: /MiXeD/Path.HTML\n"; + let result = DomainCrawler::parse_clanker_rules(content, "gurt://example", "AnotherBot") + .expect("expected allow list"); + + assert_eq!( + result, + vec!["gurt://example/MiXeD/Path.HTML".to_string()] + ); + } + + #[test] + fn parse_clanker_rules_respects_disallow_all() { + let content = "User-agent: Bot\nDisallow: /\n"; + let result = DomainCrawler::parse_clanker_rules(content, "gurt://example", "Bot"); + + assert!(result.is_err()); + } } \ No newline at end of file diff --git a/site/package-lock.json b/site/package-lock.json index af56e7c..a1378af 100644 --- a/site/package-lock.json +++ b/site/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.1", "dependencies": { "@fontsource/instrument-serif": "^5.2.7", + "@sveltejs/adapter-cloudflare": "^7.2.3", "lucide-svelte": "^0.542.0" }, "devDependencies": { @@ -34,6 +35,160 @@ "vite": "^7.0.4" } }, + "node_modules/@cloudflare/kv-asset-handler": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.4.0.tgz", + "integrity": "sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA==", + "license": "MIT OR Apache-2.0", + "peer": true, + "dependencies": { + "mime": "^3.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@cloudflare/unenv-preset": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.7.4.tgz", + "integrity": "sha512-KIjbu/Dt50zseJIoOOK5y4eYpSojD9+xxkePYVK1Rg9k/p/st4YyMtz1Clju/zrenJHrOH+AAcjNArOPMwH4Bw==", + "license": "MIT OR Apache-2.0", + "peer": true, + "peerDependencies": { + "unenv": "2.0.0-rc.21", + "workerd": "^1.20250912.0" + }, + "peerDependenciesMeta": { + "workerd": { + "optional": true + } + } + }, + "node_modules/@cloudflare/workerd-darwin-64": { + "version": "1.20250924.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20250924.0.tgz", + "integrity": "sha512-/+nWoNDIzdQaQib7MrWYEfeDt1vA40Ah68nXlZGXHonkIqJvkjaTP8dzdKZLuwnQokiV/SpnAXNMH0WGH31XMw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-darwin-arm64": { + "version": "1.20250924.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20250924.0.tgz", + "integrity": "sha512-UAjC5mra+WNWy6jMbIDe9orsFmYvvMlfvZdUyn5p3NlQhhU6cc4FkFuXJ/bV+6oVw5hIhlLlFCTnsGatki/uHg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-64": { + "version": "1.20250924.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20250924.0.tgz", + "integrity": "sha512-IcwaoZFXGHq+yOBEj91QZH4qU61ws5upE7T43wVcrUAk8VXgxL12IGUVkMCEqfFXTO40PjKZBmK16B2q1HoFow==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-arm64": { + "version": "1.20250924.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20250924.0.tgz", + "integrity": "sha512-NgKG/cJiRNoJFa8QqweG0/bpkrUYKpR9mA9/qLJcGiwfvJrfK9b+ucw0lCru1BVMlyuS3kWDjagjMWqfujdBkA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-windows-64": { + "version": "1.20250924.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20250924.0.tgz", + "integrity": "sha512-PntewemtjgLO2+8Gjw3G/NowDjpWZNKpKk/n4KmOQaWS9jIRq3IG1LkTqxj/BbMXqa4Oyrywk2kdqspj6QllOw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workers-types": { + "version": "4.20250926.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20250926.0.tgz", + "integrity": "sha512-DfnfgmV7glnvvSx/BzXHG7FlF08YvZTM7P6RcC3+mRmV4E53N2uLWE4HKBCA16yTf5HFqB18X5Cw3DjyyRTvKA==", + "license": "MIT OR Apache-2.0" + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz", + "integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.9", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", @@ -41,7 +196,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -58,7 +212,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -75,7 +228,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -92,7 +244,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -109,7 +260,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -126,7 +276,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -143,7 +292,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -160,7 +308,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -177,7 +324,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -194,7 +340,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -211,7 +356,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -228,7 +372,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -245,7 +388,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -262,7 +404,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -279,7 +420,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -296,7 +436,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -313,7 +452,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -330,7 +468,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -347,7 +484,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -364,7 +500,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -381,7 +516,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -398,7 +532,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -415,7 +548,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -432,7 +564,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -449,7 +580,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -466,7 +596,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -513,6 +642,386 @@ "url": "https://github.com/sponsors/ayuhito" } }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.5" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "peer": true, + "dependencies": { + "@emnapi/runtime": "^1.2.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@internationalized/date": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.9.0.tgz", @@ -595,9 +1104,37 @@ "version": "1.0.0-next.29", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", - "dev": true, "license": "MIT" }, + "node_modules/@poppinss/colors": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.5.tgz", + "integrity": "sha512-FvdDqtcRCtz6hThExcFOgW0cWX+xwSMWcRuQe5ZEb2m7cVQOAVZOIMt+/v9RxGiD9/OY16qJBXK4CVKWAPalBw==", + "license": "MIT", + "peer": true, + "dependencies": { + "kleur": "^4.1.5" + } + }, + "node_modules/@poppinss/dumper": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@poppinss/dumper/-/dumper-0.6.4.tgz", + "integrity": "sha512-iG0TIdqv8xJ3Lt9O8DrPRxw1MRLjNpoqiSGU03P/wNLP/s0ra0udPJ1J2Tx5M0J3H/cVyEgpbn8xUKRY9j59kQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@poppinss/colors": "^4.1.5", + "@sindresorhus/is": "^7.0.2", + "supports-color": "^10.0.0" + } + }, + "node_modules/@poppinss/exception": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@poppinss/exception/-/exception-1.2.2.tgz", + "integrity": "sha512-m7bpKCD4QMlFCjA/nKTs23fuvoVFoA83brRKmObCUNmi/9tVu8Ve3w4YQAnJu4q3Tjf5fr685HYIC/IA2zHRSg==", + "license": "MIT", + "peer": true + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.50.1", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.50.1.tgz", @@ -605,7 +1142,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -619,7 +1155,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -633,7 +1168,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -647,7 +1181,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -661,7 +1194,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -675,7 +1207,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -689,7 +1220,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -703,7 +1233,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -717,7 +1246,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -731,7 +1259,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -745,7 +1272,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -759,7 +1285,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -773,7 +1298,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -787,7 +1311,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -801,7 +1324,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -815,7 +1337,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -829,7 +1350,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -843,7 +1363,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -857,7 +1376,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -871,7 +1389,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -885,18 +1402,36 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "win32" ] }, + "node_modules/@sindresorhus/is": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.1.0.tgz", + "integrity": "sha512-7F/yz2IphV39hiS2zB4QYVkivrptHHh0K8qJJd9HhuWSdvf8AN7NpebW3CcDZDBQsUPMoDKWsY2WWgW7bqOcfA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@speed-highlight/core": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.7.tgz", + "integrity": "sha512-0dxmVj4gxg3Jg879kvFS/msl4s9F3T9UXC1InxgOf7t5NvcPD97u/WTA5vL/IxWHMn7qSxBozqrnnE2wvl1m8g==", + "license": "CC0-1.0", + "peer": true + }, "node_modules/@standard-schema/spec": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", - "dev": true, "license": "MIT" }, "node_modules/@sveltejs/acorn-typescript": { @@ -918,11 +1453,24 @@ "@sveltejs/kit": "^2.0.0" } }, + "node_modules/@sveltejs/adapter-cloudflare": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-cloudflare/-/adapter-cloudflare-7.2.3.tgz", + "integrity": "sha512-HlNqRoe1Y6n3P2EdFuX7pXWcMUTVk5b+RlGCFSa86kJMXNUON9LzxOMqI4qof9ef28BbMNCEUoucizD4yg7BtA==", + "license": "MIT", + "dependencies": { + "@cloudflare/workers-types": "^4.20250507.0", + "worktop": "0.8.0-next.18" + }, + "peerDependencies": { + "@sveltejs/kit": "^2.0.0", + "wrangler": "^4.0.0" + } + }, "node_modules/@sveltejs/kit": { "version": "2.37.1", "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.37.1.tgz", "integrity": "sha512-4T9rF2Roe7RGvHfcn6+n92Yc2NF88k7ljFz9+wE0jWxyencqRpadr2/CvlcQbbTXf1ozmFxgMO6af+qm+1mPFw==", - "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.0.0", @@ -961,7 +1509,6 @@ "version": "6.1.4", "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.1.4.tgz", "integrity": "sha512-4jfkfvsGI+U2OhHX8OPCKtMCf7g7ledXhs3E6UcA4EY0jQWsiVbe83pTAHp9XTifzYNOiD4AJieJUsI0qqxsbw==", - "dev": true, "license": "MIT", "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", @@ -982,7 +1529,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-5.0.1.tgz", "integrity": "sha512-ubWshlMk4bc8mkwWbg6vNvCeT7lGQojE3ijDh3QTR6Zr/R+GXxsGbyH4PExEPpiFmqPhYiVSVmHBjUcVc1JIrA==", - "dev": true, "license": "MIT", "dependencies": { "debug": "^4.4.1" @@ -1303,7 +1849,6 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", - "dev": true, "license": "MIT" }, "node_modules/@types/estree": { @@ -1324,6 +1869,16 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/aria-query": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", @@ -1367,6 +1922,13 @@ "svelte": "^5.33.0" } }, + "node_modules/blake3-wasm": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz", + "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==", + "license": "MIT", + "peer": true + }, "node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -1402,11 +1964,55 @@ "node": ">=6" } }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "peer": true, + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT", + "peer": true + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "peer": true, + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, "node_modules/cookie": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -1429,7 +2035,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -1447,17 +2052,22 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" } }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "license": "MIT", + "peer": true + }, "node_modules/detect-libc": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">=8" @@ -1467,7 +2077,6 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.3.2.tgz", "integrity": "sha512-UDsjUbpQn9kvm68slnrs+mfxwFkIflOhkanmyabZ8zOYk8SMEIbJ3TK+88g70hSIeytu4y18f0z/hYHMTrXIWw==", - "dev": true, "license": "MIT" }, "node_modules/enhanced-resolve": { @@ -1484,11 +2093,20 @@ "node": ">=10.13.0" } }, + "node_modules/error-stack-parser-es": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", + "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/esbuild": { "version": "0.25.9", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", - "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -1541,11 +2159,30 @@ "@jridgewell/sourcemap-codec": "^1.4.15" } }, + "node_modules/exit-hook": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-2.2.1.tgz", + "integrity": "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/exsolve": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", + "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==", + "license": "MIT", + "peer": true + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -1563,7 +2200,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -1574,6 +2210,13 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "license": "BSD-2-Clause", + "peer": true + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -1588,6 +2231,13 @@ "dev": true, "license": "MIT" }, + "node_modules/is-arrayish": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", + "license": "MIT", + "peer": true + }, "node_modules/is-reference": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", @@ -1601,7 +2251,7 @@ "version": "2.5.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", - "dev": true, + "devOptional": true, "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" @@ -1611,7 +2261,6 @@ "version": "4.1.5", "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -1621,7 +2270,7 @@ "version": "1.30.1", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", - "dev": true, + "devOptional": true, "license": "MPL-2.0", "dependencies": { "detect-libc": "^2.0.3" @@ -1653,7 +2302,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1674,7 +2322,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1695,7 +2342,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1716,7 +2362,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1737,7 +2382,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1758,7 +2402,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1779,7 +2422,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1800,7 +2442,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1821,7 +2462,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1842,7 +2482,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1901,6 +2540,59 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "license": "MIT", + "peer": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/miniflare": { + "version": "4.20250924.0", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20250924.0.tgz", + "integrity": "sha512-eQuWHklTeYYOil7sPPWo7Wrw86I4oac1kGAYfYcjg5dqMgMAiPUHvUWXMlTvW8ON6q33Ew23AsGDirm+Bea9ig==", + "license": "MIT", + "peer": true, + "dependencies": { + "@cspotcode/source-map-support": "0.8.1", + "acorn": "8.14.0", + "acorn-walk": "8.3.2", + "exit-hook": "2.2.1", + "glob-to-regexp": "0.4.1", + "sharp": "^0.33.5", + "stoppable": "1.1.0", + "undici": "7.14.0", + "workerd": "1.20250924.0", + "ws": "8.18.0", + "youch": "4.1.0-beta.10", + "zod": "3.22.3" + }, + "bin": { + "miniflare": "bootstrap.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/miniflare/node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/minipass": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", @@ -1944,7 +2636,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -1954,7 +2645,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -1964,14 +2654,12 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, "funding": [ { "type": "github", @@ -1986,18 +2674,37 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "license": "MIT", + "peer": true + }, + "node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "license": "MIT", + "peer": true + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "license": "MIT", + "peer": true + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -2010,7 +2717,6 @@ "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dev": true, "funding": [ { "type": "opencollective", @@ -2177,11 +2883,19 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/regexparam": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/regexparam/-/regexparam-3.0.0.tgz", + "integrity": "sha512-RSYAtP31mvYLkAHrOlh25pCNQ5hWnT106VukGaaFfuJrZFkGRX5GhUAdPqpSDXxOhA2c4akmRuplv1mRqnBn6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/rollup": { "version": "4.50.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.50.1.tgz", "integrity": "sha512-78E9voJHwnXQMiQdiqswVLZwJIzdBKJ1GdI5Zx6XwoFKUIk09/sSrr+05QFzvYb8q6Y9pPV45zzDuYa3907TZA==", - "dev": true, "license": "MIT", "dependencies": { "@types/estree": "1.0.8" @@ -2239,7 +2953,6 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", - "dev": true, "license": "MIT", "dependencies": { "mri": "^1.1.0" @@ -2248,18 +2961,79 @@ "node": ">=6" } }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/set-cookie-parser": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", - "dev": true, "license": "MIT" }, + "node_modules/sharp": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "hasInstallScript": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.5", + "@img/sharp-darwin-x64": "0.33.5", + "@img/sharp-libvips-darwin-arm64": "1.0.4", + "@img/sharp-libvips-darwin-x64": "1.0.4", + "@img/sharp-libvips-linux-arm": "1.0.5", + "@img/sharp-libvips-linux-arm64": "1.0.4", + "@img/sharp-libvips-linux-s390x": "1.0.4", + "@img/sharp-libvips-linux-x64": "1.0.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", + "@img/sharp-linux-arm": "0.33.5", + "@img/sharp-linux-arm64": "0.33.5", + "@img/sharp-linux-s390x": "0.33.5", + "@img/sharp-linux-x64": "0.33.5", + "@img/sharp-linuxmusl-arm64": "0.33.5", + "@img/sharp-linuxmusl-x64": "0.33.5", + "@img/sharp-wasm32": "0.33.5", + "@img/sharp-win32-ia32": "0.33.5", + "@img/sharp-win32-x64": "0.33.5" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", + "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", + "license": "MIT", + "peer": true, + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, "node_modules/sirv": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", - "dev": true, "license": "MIT", "dependencies": { "@polka/url": "^1.0.0-next.24", @@ -2274,12 +3048,22 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, + "node_modules/stoppable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", + "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=4", + "npm": ">=6" + } + }, "node_modules/style-to-object": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.9.tgz", @@ -2290,6 +3074,19 @@ "inline-style-parser": "0.2.4" } }, + "node_modules/supports-color": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/svelte": { "version": "5.38.7", "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.38.7.tgz", @@ -2449,7 +3246,6 @@ "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -2466,7 +3262,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -2476,7 +3271,7 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, + "devOptional": true, "license": "0BSD" }, "node_modules/tw-animate-css": { @@ -2503,6 +3298,37 @@ "node": ">=14.17" } }, + "node_modules/ufo": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", + "license": "MIT", + "peer": true + }, + "node_modules/undici": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.14.0.tgz", + "integrity": "sha512-Vqs8HTzjpQXZeXdpsfChQTlafcMQaaIwnGwLam1wudSSjlJeQ3bw1j+TLPePgrCnCpUXx7Ba5Pdpf5OBih62NQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/unenv": { + "version": "2.0.0-rc.21", + "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.21.tgz", + "integrity": "sha512-Wj7/AMtE9MRnAXa6Su3Lk0LNCfqDYgfwVjwRFVum9U7wsto1imuHqk4kTm7Jni+5A0Hn7dttL6O/zjvUvoo+8A==", + "license": "MIT", + "peer": true, + "dependencies": { + "defu": "^6.1.4", + "exsolve": "^1.0.7", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "ufo": "^1.6.1" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -2514,7 +3340,6 @@ "version": "7.1.4", "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.4.tgz", "integrity": "sha512-X5QFK4SGynAeeIt+A7ZWnApdUyHYm+pzv/8/A57LqSGcI88U6R6ipOs3uCesdc6yl7nl+zNO0t8LmqAdXcQihw==", - "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.25.0", @@ -2589,7 +3414,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz", "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==", - "dev": true, "license": "MIT", "workspaces": [ "tests/deps/*", @@ -2605,6 +3429,563 @@ } } }, + "node_modules/workerd": { + "version": "1.20250924.0", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20250924.0.tgz", + "integrity": "sha512-ovO2vwRCcMOlOm3bNwQQrVb8KDcewE/3rjfbZAYSF535BQQDUZ9dE1kyGBYlGx4W5udH3kqmOr+0YqTBLlycyA==", + "hasInstallScript": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "workerd": "bin/workerd" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "@cloudflare/workerd-darwin-64": "1.20250924.0", + "@cloudflare/workerd-darwin-arm64": "1.20250924.0", + "@cloudflare/workerd-linux-64": "1.20250924.0", + "@cloudflare/workerd-linux-arm64": "1.20250924.0", + "@cloudflare/workerd-windows-64": "1.20250924.0" + } + }, + "node_modules/worktop": { + "version": "0.8.0-next.18", + "resolved": "https://registry.npmjs.org/worktop/-/worktop-0.8.0-next.18.tgz", + "integrity": "sha512-+TvsA6VAVoMC3XDKR5MoC/qlLqDixEfOBysDEKnPIPou/NvoPWCAuXHXMsswwlvmEuvX56lQjvELLyLuzTKvRw==", + "license": "MIT", + "dependencies": { + "mrmime": "^2.0.0", + "regexparam": "^3.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.40.2.tgz", + "integrity": "sha512-wcev8GF6GU4ME5AsYY/gUehHrGdRiUCkifuyCeuVvpOtha3TMK4/s39x+jLuJBRtibpgejDekO68rqxfamf16A==", + "license": "MIT OR Apache-2.0", + "peer": true, + "dependencies": { + "@cloudflare/kv-asset-handler": "0.4.0", + "@cloudflare/unenv-preset": "2.7.4", + "blake3-wasm": "2.1.5", + "esbuild": "0.25.4", + "miniflare": "4.20250924.0", + "path-to-regexp": "6.3.0", + "unenv": "2.0.0-rc.21", + "workerd": "1.20250924.0" + }, + "bin": { + "wrangler": "bin/wrangler.js", + "wrangler2": "bin/wrangler.js" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@cloudflare/workers-types": "^4.20250924.0" + }, + "peerDependenciesMeta": { + "@cloudflare/workers-types": { + "optional": true + } + } + }, + "node_modules/wrangler/node_modules/@esbuild/aix-ppc64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz", + "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/wrangler/node_modules/@esbuild/android-arm": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.4.tgz", + "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/wrangler/node_modules/@esbuild/android-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz", + "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/wrangler/node_modules/@esbuild/android-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.4.tgz", + "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/wrangler/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz", + "integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/wrangler/node_modules/@esbuild/darwin-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz", + "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/wrangler/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz", + "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/wrangler/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz", + "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/wrangler/node_modules/@esbuild/linux-arm": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz", + "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/wrangler/node_modules/@esbuild/linux-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz", + "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/wrangler/node_modules/@esbuild/linux-ia32": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz", + "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/wrangler/node_modules/@esbuild/linux-loong64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz", + "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/wrangler/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz", + "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/wrangler/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz", + "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/wrangler/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz", + "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/wrangler/node_modules/@esbuild/linux-s390x": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz", + "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/wrangler/node_modules/@esbuild/linux-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz", + "integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/wrangler/node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz", + "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/wrangler/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz", + "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/wrangler/node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz", + "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/wrangler/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz", + "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/wrangler/node_modules/@esbuild/sunos-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz", + "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/wrangler/node_modules/@esbuild/win32-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz", + "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/wrangler/node_modules/@esbuild/win32-ia32": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz", + "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/wrangler/node_modules/@esbuild/win32-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz", + "integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/wrangler/node_modules/esbuild": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz", + "integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==", + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.4", + "@esbuild/android-arm": "0.25.4", + "@esbuild/android-arm64": "0.25.4", + "@esbuild/android-x64": "0.25.4", + "@esbuild/darwin-arm64": "0.25.4", + "@esbuild/darwin-x64": "0.25.4", + "@esbuild/freebsd-arm64": "0.25.4", + "@esbuild/freebsd-x64": "0.25.4", + "@esbuild/linux-arm": "0.25.4", + "@esbuild/linux-arm64": "0.25.4", + "@esbuild/linux-ia32": "0.25.4", + "@esbuild/linux-loong64": "0.25.4", + "@esbuild/linux-mips64el": "0.25.4", + "@esbuild/linux-ppc64": "0.25.4", + "@esbuild/linux-riscv64": "0.25.4", + "@esbuild/linux-s390x": "0.25.4", + "@esbuild/linux-x64": "0.25.4", + "@esbuild/netbsd-arm64": "0.25.4", + "@esbuild/netbsd-x64": "0.25.4", + "@esbuild/openbsd-arm64": "0.25.4", + "@esbuild/openbsd-x64": "0.25.4", + "@esbuild/sunos-x64": "0.25.4", + "@esbuild/win32-arm64": "0.25.4", + "@esbuild/win32-ia32": "0.25.4", + "@esbuild/win32-x64": "0.25.4" + } + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/yallist": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", @@ -2615,11 +3996,56 @@ "node": ">=18" } }, + "node_modules/youch": { + "version": "4.1.0-beta.10", + "resolved": "https://registry.npmjs.org/youch/-/youch-4.1.0-beta.10.tgz", + "integrity": "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@poppinss/colors": "^4.1.5", + "@poppinss/dumper": "^0.6.4", + "@speed-highlight/core": "^1.2.7", + "cookie": "^1.0.2", + "youch-core": "^0.3.3" + } + }, + "node_modules/youch-core": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/youch-core/-/youch-core-0.3.3.tgz", + "integrity": "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@poppinss/exception": "^1.2.2", + "error-stack-parser-es": "^1.0.5" + } + }, + "node_modules/youch/node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + } + }, "node_modules/zimmerframe": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz", "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==", "license": "MIT" + }, + "node_modules/zod": { + "version": "3.22.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz", + "integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/site/package.json b/site/package.json index df93a25..d15c158 100644 --- a/site/package.json +++ b/site/package.json @@ -37,6 +37,7 @@ }, "dependencies": { "@fontsource/instrument-serif": "^5.2.7", + "@sveltejs/adapter-cloudflare": "^7.2.3", "lucide-svelte": "^0.542.0" } } diff --git a/site/src/routes/download/+page.svelte b/site/src/routes/download/+page.svelte index 67aa033..6e201b5 100644 --- a/site/src/routes/download/+page.svelte +++ b/site/src/routes/download/+page.svelte @@ -22,7 +22,7 @@ } from 'lucide-svelte'; import Navbar from '$lib/components/Navbar.svelte'; - const version = '1.0.2'; + const version = '1.0.3'; diff --git a/site/static/crt.gif b/site/static/crt.gif index 1fe9291..2d32b3d 100644 Binary files a/site/static/crt.gif and b/site/static/crt.gif differ diff --git a/site/svelte.config.js b/site/svelte.config.js index 1295460..016dc45 100644 --- a/site/svelte.config.js +++ b/site/svelte.config.js @@ -1,4 +1,4 @@ -import adapter from '@sveltejs/adapter-auto'; +import adapter from '@sveltejs/adapter-cloudflare'; import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; /** @type {import('@sveltejs/kit').Config} */