From 5d86c58f1b9c61390fcb5a8b38214ea647ea06d4 Mon Sep 17 00:00:00 2001 From: iownbey Date: Thu, 6 Aug 2026 07:44:07 +0000 Subject: [PATCH 01/25] feedback --- cli/args/flags.rs | 7 + cli/schemas/config-file.v1.json | 30 ++ cli/tools/desktop.rs | 310 +++++++++++++++++- libs/cli_parser/src/convert.rs | 1 + libs/cli_parser/src/defs.rs | 3 + libs/cli_parser/src/flags.rs | 2 + libs/config/deno_json/mod.rs | 44 +++ libs/config/workspace/mod.rs | 1 + .../specs/desktop/backend_args/__test__.jsonc | 64 ++++ tests/specs/desktop/backend_args/desktop.out | 5 + .../desktop/backend_args/desktop_config.jsonc | 8 + .../desktop/backend_args/desktop_config.out | 5 + .../desktop/backend_args/desktop_hmi.out | 6 + tests/specs/desktop/backend_args/main.ts | 11 + 14 files changed, 487 insertions(+), 10 deletions(-) create mode 100644 tests/specs/desktop/backend_args/__test__.jsonc create mode 100644 tests/specs/desktop/backend_args/desktop.out create mode 100644 tests/specs/desktop/backend_args/desktop_config.jsonc create mode 100644 tests/specs/desktop/backend_args/desktop_config.out create mode 100644 tests/specs/desktop/backend_args/desktop_hmi.out create mode 100644 tests/specs/desktop/backend_args/main.ts diff --git a/cli/args/flags.rs b/cli/args/flags.rs index 59ec7202df37c1..6e41d8bdfddcff 100644 --- a/cli/args/flags.rs +++ b/cli/args/flags.rs @@ -2606,6 +2606,11 @@ supported framework (Next.js, Astro, etc.) in the current directory. .default_missing_value("xz") .help_heading(DESKTOP_HEADING), ) + .arg( + Arg::new("backend-args").long("backend-args").help( + "Flags to pass to the backend" + ).help_heading(DESKTOP_HEADING) + ) .arg(executable_ext_arg()) .arg(env_file_arg()) .arg( @@ -7015,6 +7020,7 @@ fn desktop_parse( .map(|f| f.collect::>()) .unwrap_or_default(); let exclude_unused_npm = matches.get_flag("exclude-unused-npm"); + let backend_args = matches.remove_one::("backend-args"); ext_arg_parse(flags, matches); flags.code_cache_enabled = !matches.get_flag("no-code-cache"); @@ -7036,6 +7042,7 @@ fn desktop_parse( inspect_renderer, compress, exclude_unused_npm, + backend_args, }); Ok(()) diff --git a/cli/schemas/config-file.v1.json b/cli/schemas/config-file.v1.json index 26375ebc779014..dae99dbf999e8f 100644 --- a/cli/schemas/config-file.v1.json +++ b/cli/schemas/config-file.v1.json @@ -193,6 +193,36 @@ "description": "Backend to use for the desktop app.", "enum": ["webview", "cef"] }, + "backendArgs": { + "type": "object", + "description": "Optional backend-specific arguments.", + "additionalProperties": false, + "properties": { + "cef": { + "type": "string", + "description": "Optional argument for the CEF backend." + }, + "webview": { + "type": "string", + "description": "Optional argument for the webview backend." + } + } + }, + "backend_args": { + "type": "object", + "description": "Optional backend-specific arguments (legacy underscore form).", + "additionalProperties": false, + "properties": { + "cef": { + "type": "string", + "description": "Optional argument for the CEF backend." + }, + "webview": { + "type": "string", + "description": "Optional argument for the webview backend." + } + } + }, "output": { "type": "object", "description": "Platform-specific output paths.", diff --git a/cli/tools/desktop.rs b/cli/tools/desktop.rs index 169206cdb7ea0d..220231bb4d39f3 100644 --- a/cli/tools/desktop.rs +++ b/cli/tools/desktop.rs @@ -165,6 +165,18 @@ fn apply_desktop_config_to_flags( desktop_flags.backend = Some(backend); } + if let Some(backend_args_config) = desktop_config.backend_args + && desktop_flags.backend_args.is_none() + { + let effective_backend = + desktop_flags.backend.as_deref().unwrap_or("webview"); + desktop_flags.backend_args = match effective_backend { + "cef" => backend_args_config.cef, + "webview" => backend_args_config.webview, + _ => None, + }; + } + if let Some(macos_config) = desktop_config.macos && let Some(identity) = macos_config.codesign_identity && desktop_flags.codesign_identity.is_none() @@ -573,6 +585,7 @@ async fn compile_desktop( &bundle_path, &appimage_abs, desktop_flags.target.as_deref(), + &desktop_flags, )?; appimage_abs } else if let Some(deb) = deb_output.as_deref() { @@ -654,8 +667,10 @@ fn make_self_extracting( }; match target_os { "macos" => make_self_extracting_macos(bundle_path, format, desktop_flags), - "windows" => make_self_extracting_dir(bundle_path, format, true), - _ => make_self_extracting_dir(bundle_path, format, false), + "windows" => { + make_self_extracting_dir(bundle_path, format, true, desktop_flags) + } + _ => make_self_extracting_dir(bundle_path, format, false, desktop_flags), } } @@ -983,6 +998,12 @@ fn make_self_extracting_macos( let (raw, comp) = write_tar_compressed(staging.path(), &inner_name, &payload, format)?; let hash = payload_hash(&payload)?; + let backend_args = format_backend_args_for_shell(desktop_flags); + let launcher_args = if backend_args.is_empty() { + String::new() + } else { + format!(" {backend_args}") + }; let launcher = format!( "#!/bin/sh\n\ @@ -994,7 +1015,7 @@ fn make_self_extracting_macos( \u{20} mkdir -p \"$DEST\"\n\ \u{20} tar -xf \"$DIR/../Resources/{payload_name}\" -C \"$DEST\"\n\ fi\n\ - exec \"$APP/Contents/MacOS/{app_name}\" \"$@\"\n", + exec \"$APP/Contents/MacOS/{app_name}\"{launcher_args} \"$@\"\n", ); let launcher_path = macos_dir.join(&app_name); std::fs::write(&launcher_path, launcher)?; @@ -1043,6 +1064,7 @@ fn make_self_extracting_dir( bundle_path: &Path, format: &str, windows: bool, + desktop_flags: &DesktopFlags, ) -> Result<(), AnyError> { let app_name = bundle_path .file_name() @@ -1066,6 +1088,12 @@ fn make_self_extracting_dir( let hash = payload_hash(&payload)?; if windows { + let backend_args = format_backend_args_for_cmd(desktop_flags); + let launcher_args = if backend_args.is_empty() { + String::new() + } else { + format!(" {backend_args}") + }; let launcher = format!( "@echo off\r\n\ setlocal\r\n\ @@ -1075,10 +1103,16 @@ fn make_self_extracting_dir( \u{20} mkdir \"%DEST%\" 2>nul\r\n\ \u{20} tar -xf \"%DIR%{payload_name}\" -C \"%DEST%\"\r\n\ )\r\n\ - \"%DEST%\\{app_name}\\{app_name}.exe\" %*\r\n", + \"%DEST%\\{app_name}\\{app_name}.exe\"{launcher_args} %*\r\n", ); std::fs::write(bundle_path.join(format!("{app_name}.bat")), launcher)?; } else { + let backend_args = format_backend_args_for_shell(desktop_flags); + let launcher_args = if backend_args.is_empty() { + String::new() + } else { + format!(" {backend_args}") + }; let launcher = format!( "#!/bin/sh\n\ set -e\n\ @@ -1089,7 +1123,7 @@ fn make_self_extracting_dir( \u{20} mkdir -p \"$DEST\"\n\ \u{20} tar -xf \"$DIR/{payload_name}\" -C \"$DEST\"\n\ fi\n\ - exec \"$APP/{app_name}\" \"$@\"\n", + exec \"$APP/{app_name}\"{launcher_args} \"$@\"\n", ); let launcher_path = bundle_path.join(&app_name); std::fs::write(&launcher_path, launcher)?; @@ -1224,6 +1258,102 @@ async fn spawn_framework_dev_server( Ok((url, child)) } +fn split_backend_args(backend_args: &str) -> Option> { + let mut candidates = vec![backend_args.to_string()]; + + if let Some(stripped) = backend_args + .strip_prefix('"') + .and_then(|s| s.strip_suffix('"')) + { + candidates.push(stripped.to_string()); + } else if let Some(stripped) = backend_args + .strip_prefix('\'') + .and_then(|s| s.strip_suffix('\'')) + { + candidates.push(stripped.to_string()); + } + + for candidate in candidates { + let normalized = candidate.replace(r#"\""#, "\"").replace(r"\'", "'"); + if let Some(tokens) = shlex::split(&normalized) { + return Some(tokens); + } + } + + None +} + +fn filtered_backend_args(desktop_flags: &DesktopFlags) -> Vec { + let Some(backend_args) = desktop_flags.backend_args.as_deref() else { + return Vec::new(); + }; + + let Some(tokens) = split_backend_args(backend_args) else { + log::warn!( + "Ignoring malformed backend args {:?}: could not parse shell syntax", + backend_args, + ); + return Vec::new(); + }; + + let mut forwarded = Vec::new(); + let mut iter = tokens.into_iter().peekable(); + while let Some(token) = iter.next() { + match token.as_str() { + _ if token.starts_with("--remote-debugging-port") => { + log::warn!( + "Ignoring --remote-debugging-port in backend args; Deno handles the remote debugging port" + ); + } + _ if token == "--runtime" => { + log::warn!( + "Ignoring --runtime in backend args; Deno handles the runtime path" + ); + let _ = iter.next(); + } + _ => { + forwarded.push(token); + } + } + } + + forwarded +} + +fn apply_backend_flags( + cmd: &mut std::process::Command, + desktop_flags: &DesktopFlags, +) { + for token in filtered_backend_args(desktop_flags) { + cmd.arg(token); + } +} + +fn format_backend_args_for_shell(desktop_flags: &DesktopFlags) -> String { + filtered_backend_args(desktop_flags) + .into_iter() + .map(|arg| { + shlex::try_quote(&arg) + .unwrap_or_else(|_| { + std::borrow::Cow::Owned(format!("\"{}\"", arg.replace('"', "\\\""))) + }) + .into_owned() + }) + .collect::>() + .join(" ") +} + +fn format_backend_args_for_cmd(desktop_flags: &DesktopFlags) -> String { + filtered_backend_args(desktop_flags) + .into_iter() + .map(|arg| { + let escaped = arg.replace('%', "%%").replace('"', "\"\""); + format!("\"{escaped}\"") + }) + .collect::>() + .join(" ") +} + /// Launch the desktop app with HMR enabled after compilation. /// /// Framework dev servers provide HMR via websocket. Since they run inside @@ -1324,6 +1454,8 @@ async fn run_desktop_hmr( cmd.env("DENO_DESKTOP_HMR", &source_abs); } + apply_backend_flags(&mut cmd, desktop_flags); + let _dev_server_child = if desktop_flags.hmr && let Some(fw) = framework && let Some(dev_cmd) = &fw.hmr_command @@ -1754,11 +1886,35 @@ async fn package_linux_app_dir( std::fs::copy(dylib_path, &dest_dylib)?; // Rename the LAUFEY backend binary to the app name so `` is the launcher - // the user runs directly — no `--runtime` argument and no shell wrapper. + // the user runs directly. When backend args are present, we add a tiny shell + // wrapper so those flags reach the backend while still pointing it at the + // colocated runtime. + let backend_args = format_backend_args_for_shell(desktop_flags); + let launcher_args = if backend_args.is_empty() { + String::new() + } else { + format!(" {backend_args}") + }; + let launcher_path = app_dir.join(&app_name); let staged_backend = app_dir.join(&laufey_binary_name); - if staged_backend != launcher_path { - std::fs::rename(&staged_backend, &launcher_path)?; + if backend_args.is_empty() { + if staged_backend != launcher_path { + std::fs::rename(&staged_backend, &launcher_path)?; + } + } else { + let wrapped_backend = app_dir.join(format!("{app_name}.bin")); + if staged_backend != wrapped_backend { + std::fs::rename(&staged_backend, &wrapped_backend)?; + } + let launcher = format!( + "#!/bin/sh\n\ + set -e\n\ + DIR=\"$(cd \"$(dirname \"$0\")\" && pwd)\"\n\ + export LAUFEY_RUNTIME_PATH=\"$DIR/{app_name}.so\"\n\ + exec \"$DIR/{app_name}.bin\"{launcher_args} \"$@\"\n", + ); + std::fs::write(&launcher_path, launcher)?; } #[cfg(unix)] { @@ -3448,6 +3604,7 @@ fn create_linux_appimage( app_dir: &Path, appimage_path: &Path, target: Option<&str>, + desktop_flags: &DesktopFlags, ) -> Result<(), AnyError> { use std::io::Cursor; use std::io::Write as _; @@ -3473,10 +3630,16 @@ fn create_linux_appimage( // AppRun is what the AppImage invokes on launch. Thin shell shim that // delegates to the existing launcher (which already sets $DIR and execs // the backend with the right args). + let backend_args = format_backend_args_for_shell(desktop_flags); + let launcher_args = if backend_args.is_empty() { + String::new() + } else { + format!(" {backend_args}") + }; let apprun = format!( "#!/bin/sh\n\ DIR=\"$(cd \"$(dirname \"$0\")\" && pwd)\"\n\ - exec \"$DIR/{app_name}\" \"$@\"\n", + exec \"$DIR/{app_name}\"{launcher_args} \"$@\"\n", ); writer.push_file( Cursor::new(apprun.into_bytes()), @@ -5460,6 +5623,8 @@ mod disclaim_spawn { #[cfg(test)] mod tests { + use deno_config::deno_json::DesktopBackendArgsConfig; + use super::*; // --- macOS Info.plist --- @@ -6746,6 +6911,7 @@ def456 other.zip source_file: String::new(), output: None, args: vec![], + backend_args: None, target: None, icon: None, include: vec![], @@ -6776,7 +6942,31 @@ def456 other.zip let app_dir = fake_linux_app_dir(tmp.path(), "MyApp"); let appimage_path = tmp.path().join("MyApp.AppImage"); let target = Some("x86_64-unknown-linux-gnu"); - create_linux_appimage(&app_dir, &appimage_path, target).unwrap(); + create_linux_appimage( + &app_dir, + &appimage_path, + target, + &DesktopFlags { + source_file: ".".to_string(), + output: None, + args: Vec::new(), + backend_args: None, + target: None, + icon: None, + include: Vec::new(), + exclude: Vec::new(), + hmr: true, + backend: None, + all_targets: false, + identifier: None, + deep_links: Vec::new(), + codesign_identity: None, + inspect_renderer: None, + compress: None, + exclude_unused_npm: false, + }, + ) + .unwrap(); let runtime_offset = appimage_runtime_for_target(target).unwrap().len() as u64; @@ -7561,4 +7751,104 @@ def456 other.zip // Left unset; callers fall back to "webview" via unwrap_or("webview"). assert_eq!(flags.backend.as_deref(), None); } + + // --- desktop.backendArgs config merge (CLI flag > deno.json) --- + + #[test] + fn backend_args_with_quoted_inner_value_are_split() { + let flags = DesktopFlags { + source_file: "main.ts".to_string(), + backend_args: Some("\"--user-agent=\\\"potato\\\"\"".to_string()), + ..Default::default() + }; + + assert_eq!(filtered_backend_args(&flags), vec!["--user-agent=potato"]); + } + + #[test] + fn backend_args_with_multiple_values_are_split() { + let flags = DesktopFlags { + source_file: "main.ts".to_string(), + backend_args: Some("\"--foo --bar\"".to_string()), + ..Default::default() + }; + + assert_eq!(filtered_backend_args(&flags), vec!["--foo", "--bar"]); + } + + #[test] + fn backend_args_from_deno_json_for_selected_backend() { + let mut flags = DesktopFlags { + source_file: "main.ts".to_string(), + backend: Some("cef".to_string()), + backend_args: None, + ..Default::default() + }; + let config = DesktopConfig { + backend_args: Some(DesktopBackendArgsConfig { + cef: Some("--enable-logging".to_string()), + webview: Some("--verbose".to_string()), + }), + ..Default::default() + }; + apply_desktop_config_to_flags(&mut flags, config); + assert_eq!(flags.backend_args.as_deref(), Some("--enable-logging")); + } + + #[test] + fn backend_args_default_to_webview_when_backend_unset() { + let mut flags = DesktopFlags { + source_file: "main.ts".to_string(), + backend: None, + backend_args: None, + ..Default::default() + }; + let config = DesktopConfig { + backend_args: Some(DesktopBackendArgsConfig { + cef: Some("--enable-logging".to_string()), + webview: Some("--verbose".to_string()), + }), + ..Default::default() + }; + apply_desktop_config_to_flags(&mut flags, config); + assert_eq!(flags.backend_args.as_deref(), Some("--verbose")); + } + + #[test] + fn cli_backend_args_override_deno_json() { + let mut flags = DesktopFlags { + source_file: "main.ts".to_string(), + backend: Some("cef".to_string()), + backend_args: Some("--from-cli".to_string()), + ..Default::default() + }; + let config = DesktopConfig { + backend_args: Some(DesktopBackendArgsConfig { + cef: Some("--enable-logging".to_string()), + webview: None, + }), + ..Default::default() + }; + apply_desktop_config_to_flags(&mut flags, config); + assert_eq!(flags.backend_args.as_deref(), Some("--from-cli")); + } + + #[test] + fn backend_args_ignored_for_unknown_backend() { + let mut flags = DesktopFlags { + source_file: "main.ts".to_string(), + backend: Some("custom".to_string()), + backend_args: None, + ..Default::default() + }; + let config = DesktopConfig { + backend_args: Some(DesktopBackendArgsConfig { + cef: Some("--enable-logging".to_string()), + webview: Some("--verbose".to_string()), + }), + ..Default::default() + }; + apply_desktop_config_to_flags(&mut flags, config); + assert_eq!(flags.backend_args.as_deref(), None); + } } diff --git a/libs/cli_parser/src/convert.rs b/libs/cli_parser/src/convert.rs index 45f83f115216a5..994e72a28c70f9 100644 --- a/libs/cli_parser/src/convert.rs +++ b/libs/cli_parser/src/convert.rs @@ -2592,6 +2592,7 @@ fn desktop_parse(result: &ParseResult, flags: &mut Flags) { inspect_renderer, compress, exclude_unused_npm: result.get_bool("exclude-unused-npm"), + backend_args: result.get_one("backend-args").map(|s| s.to_string()), }); } diff --git a/libs/cli_parser/src/defs.rs b/libs/cli_parser/src/defs.rs index 6dd503eaab7504..e0a752875dba02 100644 --- a/libs/cli_parser/src/defs.rs +++ b/libs/cli_parser/src/defs.rs @@ -2589,6 +2589,9 @@ pub static DESKTOP_SUBCOMMAND: CommandDef = CommandDef { .action(ArgAction::Append) .num_args(NumArgs::Optional) .require_equals(), + ArgDef::new("backend-args") + .long("backend-args") + .num_args(NumArgs::Optional), ], arg_groups: &[ UNSTABLE_ARGS, diff --git a/libs/cli_parser/src/flags.rs b/libs/cli_parser/src/flags.rs index 84c76694a8ff36..a4d63dd8d3f911 100644 --- a/libs/cli_parser/src/flags.rs +++ b/libs/cli_parser/src/flags.rs @@ -252,6 +252,8 @@ pub struct DesktopFlags { /// the full managed snapshot. Same opt-in semantics as /// `deno compile --exclude-unused-npm`. pub exclude_unused_npm: bool, + /// Optional flags to be passed to the chosen laufey backend + pub backend_args: Option, } #[derive(Clone)] diff --git a/libs/config/deno_json/mod.rs b/libs/config/deno_json/mod.rs index 5d733e5f0bee06..877d9066829e23 100644 --- a/libs/config/deno_json/mod.rs +++ b/libs/config/deno_json/mod.rs @@ -925,6 +925,13 @@ struct SerializedDesktopAppConfig { pub deep_links: Option>, } +#[derive(Clone, Debug, Default, Deserialize, PartialEq)] +#[serde(default, deny_unknown_fields)] +struct SerializedDesktopBackendArgsConfig { + pub cef: Option, + pub webview: Option, +} + #[derive(Clone, Debug, Default, Deserialize, PartialEq)] #[serde(default, deny_unknown_fields)] struct SerializedDesktopOutputConfig { @@ -962,6 +969,8 @@ struct SerializedDesktopMacOSConfig { struct SerializedDesktopConfig { pub app: Option, pub backend: Option, + #[serde(alias = "backendArgs")] + pub backend_args: Option, pub output: Option, pub release: Option, #[serde(rename = "errorReporting")] @@ -1005,6 +1014,10 @@ impl SerializedDesktopConfig { }), }), backend: self.backend, + backend_args: self.backend_args.map(|o| DesktopBackendArgsConfig { + cef: o.cef, + webview: o.webview, + }), output: self.output.map(|o| DesktopOutputConfig { macos: o.macos, windows: o.windows, @@ -1052,6 +1065,12 @@ pub struct DesktopAppConfig { pub deep_links: Option>, } +#[derive(Clone, Debug, Default, PartialEq)] +pub struct DesktopBackendArgsConfig { + pub cef: Option, + pub webview: Option, +} + #[derive(Clone, Debug, Default, PartialEq)] pub struct DesktopOutputConfig { pub macos: Option, @@ -1078,6 +1097,7 @@ pub struct DesktopMacOSConfig { pub struct DesktopConfig { pub app: Option, pub backend: Option, + pub backend_args: Option, pub output: Option, pub release: Option, pub error_reporting: Option, @@ -2886,6 +2906,30 @@ mod tests { assert!(error.to_string().contains("404.json")); } + #[test] + fn desktop_config_accepts_backend_args_camel_case() { + let config_text = r#"{ + "desktop": { + "backend": "cef", + "backendArgs": { + "cef": "--user-agent=potato" + } + } + }"#; + let config_specifier = Url::parse("file:///deno/deno.json").unwrap(); + let config_file = ConfigFile::new(config_text, config_specifier).unwrap(); + + let desktop_config = config_file.to_desktop_config().unwrap(); + assert_eq!(desktop_config.backend, Some("cef".to_string())); + assert_eq!( + desktop_config.backend_args, + Some(DesktopBackendArgsConfig { + cef: Some("--user-agent=potato".to_string()), + webview: None, + }) + ); + } + #[test] fn test_parse_config() { let config_text = r#"{ diff --git a/libs/config/workspace/mod.rs b/libs/config/workspace/mod.rs index 0fddfd4ec720d2..81ad325b51938b 100644 --- a/libs/config/workspace/mod.rs +++ b/libs/config/workspace/mod.rs @@ -2572,6 +2572,7 @@ impl WorkspaceDirectory { Ok(DesktopConfig { app: member_config.app.or(root_config.app), backend: member_config.backend.or(root_config.backend), + backend_args: member_config.backend_args.or(root_config.backend_args), output: member_config.output.or(root_config.output), release: member_config.release.or(root_config.release), error_reporting: member_config diff --git a/tests/specs/desktop/backend_args/__test__.jsonc b/tests/specs/desktop/backend_args/__test__.jsonc new file mode 100644 index 00000000000000..2c5102d3af08c9 --- /dev/null +++ b/tests/specs/desktop/backend_args/__test__.jsonc @@ -0,0 +1,64 @@ +{ + "tempDir": true, + "tests": { + "forwards_backend_flags_to_launcher": { + "steps": [ + { + "if": "unix", + "args": [ + "desktop", + "--backend=cef", + "--backend-args=\"--user-agent=\\\"potato\\\"\"", + "--output", + "./hello", + "./main.ts" + ], + "output": "[WILDCARD]" + }, + { + "if": "unix", + "commandName": "./hello/hello", + "args": [], + "output": "desktop.out" + } + ] + }, + "forwards_backend_flags_to_launcher_hmi": { + "steps": [ + { + "if": "unix", + "args": [ + "desktop", + "--hmr", + "--backend=cef", + "--backend-args=\"--user-agent=\\\"potato\\\"\"", + "./main.ts" + ], + "output": "desktop_hmi.out" + } + ] + }, + "forwards_backend_flags_to_launcher_config": { + "steps": [ + { + "if": "unix", + "args": [ + "desktop", + "--output", + "./hello2", + "--config", + "./desktop_config.jsonc", + "./main.ts" + ], + "output": "[WILDCARD]" + }, + { + "if": "unix", + "commandName": "./hello2/hello2", + "args": [], + "output": "desktop_config.out" + } + ] + } + } +} diff --git a/tests/specs/desktop/backend_args/desktop.out b/tests/specs/desktop/backend_args/desktop.out new file mode 100644 index 00000000000000..8d7da73430c920 --- /dev/null +++ b/tests/specs/desktop/backend_args/desktop.out @@ -0,0 +1,5 @@ +Runtime loaded successfully from: [WILDCARD]/hello/hello.so +Runtime started +[desktop] dylib path: "[WILDCARD]/hello/hello.so" +Listening on http://127.0.0.1:[WILDCARD]/ +potato diff --git a/tests/specs/desktop/backend_args/desktop_config.jsonc b/tests/specs/desktop/backend_args/desktop_config.jsonc new file mode 100644 index 00000000000000..973af79ef16295 --- /dev/null +++ b/tests/specs/desktop/backend_args/desktop_config.jsonc @@ -0,0 +1,8 @@ +{ + "desktop": { + "backend": "cef", + "backendArgs": { + "cef": "--user-agent=potato" + } + } +} diff --git a/tests/specs/desktop/backend_args/desktop_config.out b/tests/specs/desktop/backend_args/desktop_config.out new file mode 100644 index 00000000000000..7719b7da82a46b --- /dev/null +++ b/tests/specs/desktop/backend_args/desktop_config.out @@ -0,0 +1,5 @@ +Runtime loaded successfully from: [WILDCARD]/hello2/hello2.so +Runtime started +[desktop] dylib path: "[WILDCARD]/hello2/hello2.so" +Listening on http://127.0.0.1:[WILDCARD]/ +potato diff --git a/tests/specs/desktop/backend_args/desktop_hmi.out b/tests/specs/desktop/backend_args/desktop_hmi.out new file mode 100644 index 00000000000000..5da293ed94cd76 --- /dev/null +++ b/tests/specs/desktop/backend_args/desktop_hmi.out @@ -0,0 +1,6 @@ +[WILDCARD] +Runtime loaded successfully from: [WILDCARD]/[WILDCARD].so +Runtime started +[desktop] dylib path: "[WILDCARD]/[WILDCARD].so" +Listening on http://127.0.0.1:[WILDCARD]/ +potato diff --git a/tests/specs/desktop/backend_args/main.ts b/tests/specs/desktop/backend_args/main.ts new file mode 100644 index 00000000000000..5d7a95904e587a --- /dev/null +++ b/tests/specs/desktop/backend_args/main.ts @@ -0,0 +1,11 @@ +Deno.serve((req) => { + const userAgent = req.headers.get("user-agent"); + if (userAgent) { + console.log(userAgent); + Deno.exit(); + } else { + return new Response("", { + headers: { "content-type": "text/html" }, + }); + } +}); From 6c4a62a99dceef49774af3d9d8095b1a8db778ec Mon Sep 17 00:00:00 2001 From: iownbey Date: Thu, 6 Aug 2026 00:52:37 -0700 Subject: [PATCH 02/25] submodule --- tests/node_compat/runner/suite | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/node_compat/runner/suite b/tests/node_compat/runner/suite index f287abd8976855..195065d326100e 160000 --- a/tests/node_compat/runner/suite +++ b/tests/node_compat/runner/suite @@ -1 +1 @@ -Subproject commit f287abd897685505b021996828004a917f715a0f +Subproject commit 195065d326100e6fabbd9f90ffe09d0f19cb97f6 From 6dbd1b560b0c263634ff6666593e9184e0f73784 Mon Sep 17 00:00:00 2001 From: iownbey Date: Thu, 6 Aug 2026 01:16:42 -0700 Subject: [PATCH 03/25] update from merge --- cli/tools/desktop.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cli/tools/desktop.rs b/cli/tools/desktop.rs index e56b86a00de327..65432a2d20a558 100644 --- a/cli/tools/desktop.rs +++ b/cli/tools/desktop.rs @@ -6940,6 +6940,7 @@ def456 other.zip #[test] fn appimage_uses_runtime_supported_zstd_squashfs() { + use crate::args::JavaScriptEngine; let tmp = tempfile::tempdir().unwrap(); let app_dir = fake_linux_app_dir(tmp.path(), "MyApp"); let appimage_path = tmp.path().join("MyApp.AppImage"); @@ -6966,6 +6967,7 @@ def456 other.zip inspect_renderer: None, compress: None, exclude_unused_npm: false, + engine: JavaScriptEngine::V8, }, ) .unwrap(); From 84e8414d4103f4c5f7b20acec0192c7c815a2b3f Mon Sep 17 00:00:00 2001 From: iownbey Date: Fri, 7 Aug 2026 05:40:40 +0000 Subject: [PATCH 04/25] remove duplicate config key --- cli/schemas/config-file.v1.json | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/cli/schemas/config-file.v1.json b/cli/schemas/config-file.v1.json index dae99dbf999e8f..8150bd5a61a4f1 100644 --- a/cli/schemas/config-file.v1.json +++ b/cli/schemas/config-file.v1.json @@ -208,21 +208,6 @@ } } }, - "backend_args": { - "type": "object", - "description": "Optional backend-specific arguments (legacy underscore form).", - "additionalProperties": false, - "properties": { - "cef": { - "type": "string", - "description": "Optional argument for the CEF backend." - }, - "webview": { - "type": "string", - "description": "Optional argument for the webview backend." - } - } - }, "output": { "type": "object", "description": "Platform-specific output paths.", From 229c49b9e72e26f2e2c67a1a12d63f51b8824b19 Mon Sep 17 00:00:00 2001 From: iownbey Date: Thu, 6 Aug 2026 22:48:32 -0700 Subject: [PATCH 05/25] linting --- tests/specs/desktop/backend_args/desktop_config.jsonc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/specs/desktop/backend_args/desktop_config.jsonc b/tests/specs/desktop/backend_args/desktop_config.jsonc index 973af79ef16295..66934962012917 100644 --- a/tests/specs/desktop/backend_args/desktop_config.jsonc +++ b/tests/specs/desktop/backend_args/desktop_config.jsonc @@ -1,8 +1,8 @@ { - "desktop": { - "backend": "cef", - "backendArgs": { - "cef": "--user-agent=potato" - } + "desktop": { + "backend": "cef", + "backendArgs": { + "cef": "--user-agent=potato" } + } } From 7b75d2120d9a59a737a1861f6f7b63f7a963b197 Mon Sep 17 00:00:00 2001 From: iownbey Date: Fri, 7 Aug 2026 00:59:30 -0700 Subject: [PATCH 06/25] fix test --- cli/tools/desktop.rs | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/cli/tools/desktop.rs b/cli/tools/desktop.rs index 65432a2d20a558..f54d870650b0ee 100644 --- a/cli/tools/desktop.rs +++ b/cli/tools/desktop.rs @@ -7758,28 +7758,6 @@ def456 other.zip // --- desktop.backendArgs config merge (CLI flag > deno.json) --- - #[test] - fn backend_args_with_quoted_inner_value_are_split() { - let flags = DesktopFlags { - source_file: "main.ts".to_string(), - backend_args: Some("\"--user-agent=\\\"potato\\\"\"".to_string()), - ..Default::default() - }; - - assert_eq!(filtered_backend_args(&flags), vec!["--user-agent=potato"]); - } - - #[test] - fn backend_args_with_multiple_values_are_split() { - let flags = DesktopFlags { - source_file: "main.ts".to_string(), - backend_args: Some("\"--foo --bar\"".to_string()), - ..Default::default() - }; - - assert_eq!(filtered_backend_args(&flags), vec!["--foo", "--bar"]); - } - #[test] fn backend_args_from_deno_json_for_selected_backend() { let mut flags = DesktopFlags { From 30fa6959410447a20699dbf2e7870daa2c6441d1 Mon Sep 17 00:00:00 2001 From: iownbey Date: Fri, 7 Aug 2026 10:34:24 -0700 Subject: [PATCH 07/25] ci --- .github/workflows/ci.generated.yml | 168 ++++++++++++++++++ .github/workflows/ci.ts | 30 ++++ cli/tools/desktop.rs | 16 +- .../specs/desktop/backend_args/__test__.jsonc | 2 + .../desktop/backend_args/desktop_hmi.out | 4 +- tests/util/lib/builders.rs | 16 ++ tests/util/lib/lib.rs | 4 + tools/download_laufey.ts | 86 +++++++++ 8 files changed, 321 insertions(+), 5 deletions(-) create mode 100644 tools/download_laufey.ts diff --git a/.github/workflows/ci.generated.yml b/.github/workflows/ci.generated.yml index b8d0b151d6e436..31f7fc040f85c5 100644 --- a/.github/workflows/ci.generated.yml +++ b/.github/workflows/ci.generated.yml @@ -614,6 +614,20 @@ jobs: [ -f "$c" ] && DENO_BIN="$c" && break done "$DENO_BIN" run -A ./tools/download_tsc.ts + - name: Set up native laufey cache + uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 + if: '!startsWith(github.ref, ''refs/tags/'') && (matrix.shard_index == 0 || github.event_name == ''pull_request'') && matrix.test_crate == ''specs''' + with: + path: ./target/.native_laufey + key: 'laufey-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles(''cli/laufey_sums.lock'') }}' + - name: Pre-download native laufey + if: '!startsWith(github.ref, ''refs/tags/'') && (matrix.shard_index == 0 || github.event_name == ''pull_request'') && matrix.test_crate == ''specs''' + run: |- + DENO_BIN="" + for c in ./target/release/deno ./target/release/deno.exe ./target/debug/deno ./target/debug/deno.exe; do + [ -f "$c" ] && DENO_BIN="$c" && break + done + "$DENO_BIN" run -A ./tools/download_laufey.ts - name: Build ffi (debug) if: '!startsWith(github.ref, ''refs/tags/'') && (matrix.shard_index == 0 || github.event_name == ''pull_request'') && matrix.test_crate == ''specs''' run: cargo build -p test_ffi @@ -1172,6 +1186,20 @@ jobs: [ -f "$c" ] && DENO_BIN="$c" && break done "$DENO_BIN" run -A ./tools/download_tsc.ts + - name: Set up native laufey cache + uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 + if: '!startsWith(github.ref, ''refs/tags/'') && !(!contains(github.event.pull_request.labels.*.name, ''ci-full'') && github.event_name == ''pull_request'') && (matrix.shard_index == 0 || github.event_name == ''pull_request'') && matrix.test_crate == ''specs''' + with: + path: ./target/.native_laufey + key: 'laufey-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles(''cli/laufey_sums.lock'') }}' + - name: Pre-download native laufey + if: '!startsWith(github.ref, ''refs/tags/'') && !(!contains(github.event.pull_request.labels.*.name, ''ci-full'') && github.event_name == ''pull_request'') && (matrix.shard_index == 0 || github.event_name == ''pull_request'') && matrix.test_crate == ''specs''' + run: |- + DENO_BIN="" + for c in ./target/release/deno ./target/release/deno.exe ./target/debug/deno ./target/debug/deno.exe; do + [ -f "$c" ] && DENO_BIN="$c" && break + done + "$DENO_BIN" run -A ./tools/download_laufey.ts - name: Build ffi (release) if: '!startsWith(github.ref, ''refs/tags/'') && !(!contains(github.event.pull_request.labels.*.name, ''ci-full'') && github.event_name == ''pull_request'') && (matrix.shard_index == 0 || github.event_name == ''pull_request'') && matrix.test_crate == ''specs''' run: cargo build --release -p test_ffi @@ -1565,6 +1593,20 @@ jobs: [ -f "$c" ] && DENO_BIN="$c" && break done "$DENO_BIN" run -A ./tools/download_tsc.ts + - name: Set up native laufey cache + uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 + if: '!startsWith(github.ref, ''refs/tags/'') && (matrix.shard_index == 0 || github.event_name == ''pull_request'') && matrix.test_crate == ''specs''' + with: + path: ./target/.native_laufey + key: 'laufey-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles(''cli/laufey_sums.lock'') }}' + - name: Pre-download native laufey + if: '!startsWith(github.ref, ''refs/tags/'') && (matrix.shard_index == 0 || github.event_name == ''pull_request'') && matrix.test_crate == ''specs''' + run: |- + DENO_BIN="" + for c in ./target/release/deno ./target/release/deno.exe ./target/debug/deno ./target/debug/deno.exe; do + [ -f "$c" ] && DENO_BIN="$c" && break + done + "$DENO_BIN" run -A ./tools/download_laufey.ts - name: Build ffi (debug) if: '!startsWith(github.ref, ''refs/tags/'') && (matrix.shard_index == 0 || github.event_name == ''pull_request'') && matrix.test_crate == ''specs''' run: cargo build -p test_ffi @@ -2323,6 +2365,20 @@ jobs: [ -f "$c" ] && DENO_BIN="$c" && break done "$DENO_BIN" run -A ./tools/download_tsc.ts + - name: Set up native laufey cache + uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 + if: '!startsWith(github.ref, ''refs/tags/'') && !(!contains(github.event.pull_request.labels.*.name, ''ci-full'') && github.event_name == ''pull_request'') && (matrix.shard_index == 0 || github.event_name == ''pull_request'') && matrix.test_crate == ''specs''' + with: + path: ./target/.native_laufey + key: 'laufey-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles(''cli/laufey_sums.lock'') }}' + - name: Pre-download native laufey + if: '!startsWith(github.ref, ''refs/tags/'') && !(!contains(github.event.pull_request.labels.*.name, ''ci-full'') && github.event_name == ''pull_request'') && (matrix.shard_index == 0 || github.event_name == ''pull_request'') && matrix.test_crate == ''specs''' + run: |- + DENO_BIN="" + for c in ./target/release/deno ./target/release/deno.exe ./target/debug/deno ./target/debug/deno.exe; do + [ -f "$c" ] && DENO_BIN="$c" && break + done + "$DENO_BIN" run -A ./tools/download_laufey.ts - name: Build ffi (release) if: '!startsWith(github.ref, ''refs/tags/'') && !(!contains(github.event.pull_request.labels.*.name, ''ci-full'') && github.event_name == ''pull_request'') && (matrix.shard_index == 0 || github.event_name == ''pull_request'') && matrix.test_crate == ''specs''' run: cargo build --release -p test_ffi @@ -2672,6 +2728,20 @@ jobs: [ -f "$c" ] && DENO_BIN="$c" && break done "$DENO_BIN" run -A ./tools/download_tsc.ts + - name: Set up native laufey cache + uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 + if: '!startsWith(github.ref, ''refs/tags/'') && (matrix.shard_index == 0 || github.event_name == ''pull_request'') && matrix.test_crate == ''specs''' + with: + path: ./target/.native_laufey + key: 'laufey-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles(''cli/laufey_sums.lock'') }}' + - name: Pre-download native laufey + if: '!startsWith(github.ref, ''refs/tags/'') && (matrix.shard_index == 0 || github.event_name == ''pull_request'') && matrix.test_crate == ''specs''' + run: |- + DENO_BIN="" + for c in ./target/release/deno ./target/release/deno.exe ./target/debug/deno ./target/debug/deno.exe; do + [ -f "$c" ] && DENO_BIN="$c" && break + done + "$DENO_BIN" run -A ./tools/download_laufey.ts - name: Build ffi (debug) if: '!startsWith(github.ref, ''refs/tags/'') && (matrix.shard_index == 0 || github.event_name == ''pull_request'') && matrix.test_crate == ''specs''' run: cargo build -p test_ffi @@ -3350,6 +3420,20 @@ jobs: [ -f "$c" ] && DENO_BIN="$c" && break done "$DENO_BIN" run -A ./tools/download_tsc.ts + - name: Set up native laufey cache + uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 + if: '!startsWith(github.ref, ''refs/tags/'') && !(!contains(github.event.pull_request.labels.*.name, ''ci-full'') && github.event_name == ''pull_request'') && (matrix.shard_index == 0 || github.event_name == ''pull_request'') && matrix.test_crate == ''specs''' + with: + path: ./target/.native_laufey + key: 'laufey-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles(''cli/laufey_sums.lock'') }}' + - name: Pre-download native laufey + if: '!startsWith(github.ref, ''refs/tags/'') && !(!contains(github.event.pull_request.labels.*.name, ''ci-full'') && github.event_name == ''pull_request'') && (matrix.shard_index == 0 || github.event_name == ''pull_request'') && matrix.test_crate == ''specs''' + run: |- + DENO_BIN="" + for c in ./target/release/deno ./target/release/deno.exe ./target/debug/deno ./target/debug/deno.exe; do + [ -f "$c" ] && DENO_BIN="$c" && break + done + "$DENO_BIN" run -A ./tools/download_laufey.ts - name: Build ffi (release) if: '!startsWith(github.ref, ''refs/tags/'') && !(!contains(github.event.pull_request.labels.*.name, ''ci-full'') && github.event_name == ''pull_request'') && (matrix.shard_index == 0 || github.event_name == ''pull_request'') && matrix.test_crate == ''specs''' run: cargo build --release -p test_ffi @@ -3699,6 +3783,20 @@ jobs: [ -f "$c" ] && DENO_BIN="$c" && break done "$DENO_BIN" run -A ./tools/download_tsc.ts + - name: Set up native laufey cache + uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 + if: '!startsWith(github.ref, ''refs/tags/'') && (matrix.shard_index == 0 || github.event_name == ''pull_request'') && matrix.test_crate == ''specs''' + with: + path: ./target/.native_laufey + key: 'laufey-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles(''cli/laufey_sums.lock'') }}' + - name: Pre-download native laufey + if: '!startsWith(github.ref, ''refs/tags/'') && (matrix.shard_index == 0 || github.event_name == ''pull_request'') && matrix.test_crate == ''specs''' + run: |- + DENO_BIN="" + for c in ./target/release/deno ./target/release/deno.exe ./target/debug/deno ./target/debug/deno.exe; do + [ -f "$c" ] && DENO_BIN="$c" && break + done + "$DENO_BIN" run -A ./tools/download_laufey.ts - name: Build ffi (debug) if: '!startsWith(github.ref, ''refs/tags/'') && (matrix.shard_index == 0 || github.event_name == ''pull_request'') && matrix.test_crate == ''specs''' run: cargo build -p test_ffi @@ -4258,6 +4356,20 @@ jobs: [ -f "$c" ] && DENO_BIN="$c" && break done "$DENO_BIN" run -A ./tools/download_tsc.ts + - name: Set up native laufey cache + uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 + if: '!startsWith(github.ref, ''refs/tags/'') && !(!contains(github.event.pull_request.labels.*.name, ''ci-full'') && github.event_name == ''pull_request'') && (matrix.shard_index == 0 || github.event_name == ''pull_request'') && matrix.test_crate == ''specs''' + with: + path: ./target/.native_laufey + key: 'laufey-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles(''cli/laufey_sums.lock'') }}' + - name: Pre-download native laufey + if: '!startsWith(github.ref, ''refs/tags/'') && !(!contains(github.event.pull_request.labels.*.name, ''ci-full'') && github.event_name == ''pull_request'') && (matrix.shard_index == 0 || github.event_name == ''pull_request'') && matrix.test_crate == ''specs''' + run: |- + DENO_BIN="" + for c in ./target/release/deno ./target/release/deno.exe ./target/debug/deno ./target/debug/deno.exe; do + [ -f "$c" ] && DENO_BIN="$c" && break + done + "$DENO_BIN" run -A ./tools/download_laufey.ts - name: Build ffi (release) if: '!startsWith(github.ref, ''refs/tags/'') && !(!contains(github.event.pull_request.labels.*.name, ''ci-full'') && github.event_name == ''pull_request'') && (matrix.shard_index == 0 || github.event_name == ''pull_request'') && matrix.test_crate == ''specs''' run: cargo build --release -p test_ffi @@ -5041,6 +5153,20 @@ jobs: [ -f "$c" ] && DENO_BIN="$c" && break done "$DENO_BIN" run -A ./tools/download_tsc.ts + - name: Set up native laufey cache + uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 + if: '!startsWith(github.ref, ''refs/tags/'') && (matrix.shard_index == 0 || github.event_name == ''pull_request'') && matrix.test_crate == ''specs''' + with: + path: ./target/.native_laufey + key: 'laufey-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles(''cli/laufey_sums.lock'') }}' + - name: Pre-download native laufey + if: '!startsWith(github.ref, ''refs/tags/'') && (matrix.shard_index == 0 || github.event_name == ''pull_request'') && matrix.test_crate == ''specs''' + run: |- + DENO_BIN="" + for c in ./target/release/deno ./target/release/deno.exe ./target/debug/deno ./target/debug/deno.exe; do + [ -f "$c" ] && DENO_BIN="$c" && break + done + "$DENO_BIN" run -A ./tools/download_laufey.ts - name: Build ffi (release) if: '!startsWith(github.ref, ''refs/tags/'') && (matrix.shard_index == 0 || github.event_name == ''pull_request'') && matrix.test_crate == ''specs''' run: cargo build --release -p test_ffi @@ -5726,6 +5852,20 @@ jobs: [ -f "$c" ] && DENO_BIN="$c" && break done "$DENO_BIN" run -A ./tools/download_tsc.ts + - name: Set up native laufey cache + uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 + if: '!startsWith(github.ref, ''refs/tags/'') && (matrix.shard_index == 0 || github.event_name == ''pull_request'') && matrix.test_crate == ''specs''' + with: + path: ./target/.native_laufey + key: 'laufey-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles(''cli/laufey_sums.lock'') }}' + - name: Pre-download native laufey + if: '!startsWith(github.ref, ''refs/tags/'') && (matrix.shard_index == 0 || github.event_name == ''pull_request'') && matrix.test_crate == ''specs''' + run: |- + DENO_BIN="" + for c in ./target/release/deno ./target/release/deno.exe ./target/debug/deno ./target/debug/deno.exe; do + [ -f "$c" ] && DENO_BIN="$c" && break + done + "$DENO_BIN" run -A ./tools/download_laufey.ts - name: Build ffi (debug) if: '!startsWith(github.ref, ''refs/tags/'') && (matrix.shard_index == 0 || github.event_name == ''pull_request'') && matrix.test_crate == ''specs''' run: cargo build -p test_ffi @@ -6224,6 +6364,20 @@ jobs: [ -f "$c" ] && DENO_BIN="$c" && break done "$DENO_BIN" run -A ./tools/download_tsc.ts + - name: Set up native laufey cache + uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 + if: '!startsWith(github.ref, ''refs/tags/'') && (matrix.shard_index == 0 || github.event_name == ''pull_request'') && matrix.test_crate == ''specs''' + with: + path: ./target/.native_laufey + key: 'laufey-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles(''cli/laufey_sums.lock'') }}' + - name: Pre-download native laufey + if: '!startsWith(github.ref, ''refs/tags/'') && (matrix.shard_index == 0 || github.event_name == ''pull_request'') && matrix.test_crate == ''specs''' + run: |- + DENO_BIN="" + for c in ./target/release/deno ./target/release/deno.exe ./target/debug/deno ./target/debug/deno.exe; do + [ -f "$c" ] && DENO_BIN="$c" && break + done + "$DENO_BIN" run -A ./tools/download_laufey.ts - name: Load 'vsock_loopback; kernel module if: '!startsWith(github.ref, ''refs/tags/'') && (matrix.shard_index == 0 || github.event_name == ''pull_request'')' run: sudo modprobe vsock_loopback @@ -7152,6 +7306,20 @@ jobs: [ -f "$c" ] && DENO_BIN="$c" && break done "$DENO_BIN" run -A ./tools/download_tsc.ts + - name: Set up native laufey cache + uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 + if: '!startsWith(github.ref, ''refs/tags/'') && !(!contains(github.event.pull_request.labels.*.name, ''ci-full'') && github.event_name == ''pull_request'') && (matrix.shard_index == 0 || github.event_name == ''pull_request'') && matrix.test_crate == ''specs''' + with: + path: ./target/.native_laufey + key: 'laufey-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles(''cli/laufey_sums.lock'') }}' + - name: Pre-download native laufey + if: '!startsWith(github.ref, ''refs/tags/'') && !(!contains(github.event.pull_request.labels.*.name, ''ci-full'') && github.event_name == ''pull_request'') && (matrix.shard_index == 0 || github.event_name == ''pull_request'') && matrix.test_crate == ''specs''' + run: |- + DENO_BIN="" + for c in ./target/release/deno ./target/release/deno.exe ./target/debug/deno ./target/debug/deno.exe; do + [ -f "$c" ] && DENO_BIN="$c" && break + done + "$DENO_BIN" run -A ./tools/download_laufey.ts - name: Load 'vsock_loopback; kernel module if: '!startsWith(github.ref, ''refs/tags/'') && !(!contains(github.event.pull_request.labels.*.name, ''ci-full'') && github.event_name == ''pull_request'') && (matrix.shard_index == 0 || github.event_name == ''pull_request'')' run: sudo modprobe vsock_loopback diff --git a/.github/workflows/ci.ts b/.github/workflows/ci.ts index d581a3e633b135..9ebb7812bb47ac 100755 --- a/.github/workflows/ci.ts +++ b/.github/workflows/ci.ts @@ -1474,6 +1474,36 @@ const buildJobs = buildItems.map((rawBuildItem) => { '"$DENO_BIN" run -A ./tools/download_tsc.ts', ].join("\n"), }, + { + name: "Set up native laufey cache", + if: testCrateNameExpr.equals("specs"), + uses: "actions/cache@v5", + with: { + // Keyed on laufey_sums.lock so a pinned-version bump re-downloads. + path: "./target/.native_laufey", + key: + "laufey-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('cli/laufey_sums.lock') }}", + }, + }, + { + // Warm the cache with the "laufey" desktop backend that `deno + // desktop` downloads (into the default target/.native_laufey) so + // the test step doesn't re-download the (100+ MB) archive for + // every test's fresh DENO_DIR. The harness resolves that path and + // injects DENO_LAUFEY_CACHE_DIR per-test itself (see + // test_util::native_laufey_cache_dir); no env export needed. Run + // it with the built deno binary (the test job has no system + // `deno` on PATH). + name: "Pre-download native laufey", + if: testCrateNameExpr.equals("specs"), + run: [ + 'DENO_BIN=""', + "for c in ./target/release/deno ./target/release/deno.exe ./target/debug/deno ./target/debug/deno.exe; do", + ' [ -f "$c" ] && DENO_BIN="$c" && break', + "done", + '"$DENO_BIN" run -A ./tools/download_laufey.ts', + ].join("\n"), + }, { if: buildItem.os.equals("linux").and( buildItem.arch.equals("aarch64"), diff --git a/cli/tools/desktop.rs b/cli/tools/desktop.rs index f54d870650b0ee..14ab1fd25b0458 100644 --- a/cli/tools/desktop.rs +++ b/cli/tools/desktop.rs @@ -2006,19 +2006,29 @@ async fn package_linux_app_dir( /// are searched the same way the old sibling-checkout heuristic searched. const LAUFEY_DEV_DIR_ENV: &str = "LAUFEY_DEV_DIR"; +/// Overrides `/laufey` with a fixed cache directory. The backend +/// archives are 100+ MB, so downloading one per test is impractical when each +/// spec test gets its own throwaway `DENO_DIR` — the test harness points every +/// test at the same on-disk cache via this var instead (see +/// `tests/util/lib/builders.rs` and `tools/download_laufey.ts`). +const LAUFEY_CACHE_DIR_ENV: &str = "DENO_LAUFEY_CACHE_DIR"; + /// Resolves LAUFEY backend binaries and `.app` bundles, falling back to /// downloading prebuilt archives from the laufey GitHub releases when /// `LAUFEY_DEV_DIR` is not set. struct LaufeyBackendResolver { http_client_provider: Arc, - /// `/laufey//` + /// `/laufey//`, or `//` + /// when the override is set. cache_root: PathBuf, } impl LaufeyBackendResolver { fn new(factory: &CliFactory) -> Result { - let cache_root = - factory.deno_dir()?.root.join("laufey").join(LAUFEY_VERSION); + let cache_root = match std::env::var_os(LAUFEY_CACHE_DIR_ENV) { + Some(dir) => PathBuf::from(dir).join(LAUFEY_VERSION), + None => factory.deno_dir()?.root.join("laufey").join(LAUFEY_VERSION), + }; Ok(Self { http_client_provider: factory.http_client_provider().clone(), cache_root, diff --git a/tests/specs/desktop/backend_args/__test__.jsonc b/tests/specs/desktop/backend_args/__test__.jsonc index 2c5102d3af08c9..94b22b4928c090 100644 --- a/tests/specs/desktop/backend_args/__test__.jsonc +++ b/tests/specs/desktop/backend_args/__test__.jsonc @@ -32,6 +32,8 @@ "--hmr", "--backend=cef", "--backend-args=\"--user-agent=\\\"potato\\\"\"", + "--output", + "./hello3", "./main.ts" ], "output": "desktop_hmi.out" diff --git a/tests/specs/desktop/backend_args/desktop_hmi.out b/tests/specs/desktop/backend_args/desktop_hmi.out index 5da293ed94cd76..620778c9954d73 100644 --- a/tests/specs/desktop/backend_args/desktop_hmi.out +++ b/tests/specs/desktop/backend_args/desktop_hmi.out @@ -1,6 +1,6 @@ [WILDCARD] -Runtime loaded successfully from: [WILDCARD]/[WILDCARD].so +Runtime loaded successfully from: [WILDCARD]/hello3.so Runtime started -[desktop] dylib path: "[WILDCARD]/[WILDCARD].so" +[desktop] dylib path: "[WILDCARD]/hello3.so" Listening on http://127.0.0.1:[WILDCARD]/ potato diff --git a/tests/util/lib/builders.rs b/tests/util/lib/builders.rs index 9a9c38a2c79125..4f3edbbf2acc65 100644 --- a/tests/util/lib/builders.rs +++ b/tests/util/lib/builders.rs @@ -304,6 +304,22 @@ impl TestContextBuilder { } } + // Same idea as `DENO_TSC_BIN` above, but for `deno desktop`'s "laufey" + // backend downloads: point every test's fresh `DENO_DIR` at one shared + // cache dir instead of re-downloading the (100+ MB) backend archive per + // test. An explicit ambient `DENO_LAUFEY_CACHE_DIR` still wins, as does a + // value a test sets itself. + if !envs.contains_key("DENO_LAUFEY_CACHE_DIR") { + let laufey_cache_dir = std::env::var_os("DENO_LAUFEY_CACHE_DIR") + .map(|v| v.to_string_lossy().into_owned()) + .unwrap_or_else(|| { + crate::native_laufey_cache_dir() + .to_string_lossy() + .into_owned() + }); + envs.insert("DENO_LAUFEY_CACHE_DIR".to_string(), laufey_cache_dir); + } + TestContext { cwd, deno_exe, diff --git a/tests/util/lib/lib.rs b/tests/util/lib/lib.rs index d1db9e00ac65f7..54031271356110 100644 --- a/tests/util/lib/lib.rs +++ b/tests/util/lib/lib.rs @@ -192,6 +192,10 @@ pub fn native_tsc_bin_path() -> Option { None } +pub fn native_laufey_cache_dir() -> PathRef { + root_path().join("target").join(".native_laufey") +} + pub fn prebuilt_path() -> PathRef { third_party_path().join("prebuilt") } diff --git a/tools/download_laufey.ts b/tools/download_laufey.ts new file mode 100644 index 00000000000000..7a30d23cd0280a --- /dev/null +++ b/tools/download_laufey.ts @@ -0,0 +1,86 @@ +#!/usr/bin/env -S deno run --allow-read --allow-write --allow-env --allow-run --allow-net +// Copyright 2018-2026 the Deno authors. MIT license. +// deno-lint-ignore-file no-console + +// Pre-downloads a "laufey" desktop backend that `deno desktop --backend=` +// launches, into a cache directory, so CI (and local test runs) don't +// re-download the (100+ MB) backend archive for every test's fresh +// `DENO_DIR`. +// +// It warms the cache by compiling (not running) a trivial desktop app with +// the built `deno` binary, which downloads + packages the backend via its +// normal path. The test harness points every spec test at the same cache +// directory via `DENO_LAUFEY_CACHE_DIR` (see +// `test_util::native_laufey_cache_dir`), so this script only needs to warm +// the cache - it does not export the path into the env. +// +// deno run -A tools/download_laufey.ts [backend] [cache_dir] +// +// For local `cargo test` runs, running it once (with the default backend +// and cache directory) is enough; the harness points every test at +// `target/.native_laufey`. To point tests at a different cache instead, +// export it explicitly: +// +// export DENO_LAUFEY_CACHE_DIR=/path/to/cache +// +// The `deno` binary to use is taken from the `DENO_BIN` env var, else the +// first of `./target/release/deno` or `./target/debug/deno` that exists. + +const exe = Deno.build.os === "windows" ? ".exe" : ""; + +// Absolute paths are required because the download runs the compiler in a +// different working directory, against which relative paths would resolve. +function absolute(p: string): string { + if (p.startsWith("/") || /^[A-Za-z]:[\\/]/.test(p)) return p; + return `${Deno.cwd()}/${p}`; +} + +function resolveDenoBin(): string { + const fromEnv = Deno.env.get("DENO_BIN"); + if (fromEnv) return Deno.realPathSync(fromEnv); + for (const profile of ["release", "debug"]) { + const candidate = `./target/${profile}/deno${exe}`; + try { + return Deno.realPathSync(candidate); + } catch { + // try the next profile + } + } + throw new Error( + "could not find a built deno binary; set DENO_BIN or build deno first", + ); +} + +const backend = Deno.args[0] ?? "cef"; +const cacheDir = absolute(Deno.args[1] ?? "./target/.native_laufey"); + +const denoBin = resolveDenoBin(); +const warmDir = Deno.makeTempDirSync(); +Deno.writeTextFileSync( + `${warmDir}/main.ts`, + `Deno.serve(() => new Response("ok"));\n`, +); + +console.error(`Downloading laufey "${backend}" backend into ${cacheDir}...`); +// `deno desktop` without `--hmr` only compiles + packages the app - it never +// launches the backend - so this is safe to run headless. +const output = new Deno.Command(denoBin, { + args: [ + "desktop", + `--backend=${backend}`, + "--output", + `${warmDir}/app`, + `${warmDir}/main.ts`, + ], + cwd: warmDir, + env: { DENO_LAUFEY_CACHE_DIR: cacheDir }, + stdout: "inherit", + stderr: "inherit", +}).outputSync(); + +if (!output.success) { + console.error(`failed to download the laufey "${backend}" backend`); + Deno.exit(1); +} + +console.log(cacheDir); From bd1949d3eef19bdc2a5041acef353707df83e666 Mon Sep 17 00:00:00 2001 From: iownbey Date: Fri, 7 Aug 2026 12:16:19 -0700 Subject: [PATCH 08/25] update ci --- Cargo.lock | 7 + Cargo.toml | 1 + cli/Cargo.toml | 1 + cli/tools/desktop.rs | 162 +++++++++++------- .../specs/desktop/backend_args/__test__.jsonc | 65 ++++++- .../desktop/backend_args/desktop_escaped.out | 5 + .../desktop/backend_args/desktop_spaces.out | 5 + 7 files changed, 182 insertions(+), 64 deletions(-) create mode 100644 tests/specs/desktop/backend_args/desktop_escaped.out create mode 100644 tests/specs/desktop/backend_args/desktop_spaces.out diff --git a/Cargo.lock b/Cargo.lock index 2816bd4957f0cf..8bec868b3a9e7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2054,6 +2054,7 @@ dependencies = [ "serde_json", "serde_repr", "sha2", + "shell-words", "shlex", "spki 0.7.3", "strsim", @@ -9422,6 +9423,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shell-words" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" + [[package]] name = "shlex" version = "1.3.0" diff --git a/Cargo.toml b/Cargo.toml index 7d95a3af4de816..9abc3d957c8eea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -309,6 +309,7 @@ serde = { version = "1.0.149", features = ["derive"] } serde_bytes = "0.11" serde_json = { version = "1.0.85", features = ["raw_value"] } serde_repr = "=0.1.19" +shell-words = "1.1.0" shlex = "1.3.0" signal-hook = "0.3" slab = "0.4" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 25ba56605628f6..e7dba7f50e4426 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -181,6 +181,7 @@ same-file.workspace = true serde.workspace = true serde_repr.workspace = true sha2.workspace = true +shell-words.workspace = true shlex.workspace = true spki = { workspace = true, features = ["pem"] } strsim.workspace = true diff --git a/cli/tools/desktop.rs b/cli/tools/desktop.rs index 14ab1fd25b0458..6fa492e4b67411 100644 --- a/cli/tools/desktop.rs +++ b/cli/tools/desktop.rs @@ -999,7 +999,7 @@ fn make_self_extracting_macos( let (raw, comp) = write_tar_compressed(staging.path(), &inner_name, &payload, format)?; let hash = payload_hash(&payload)?; - let backend_args = format_backend_args_for_shell(desktop_flags); + let backend_args = format_backend_args_for_shell(desktop_flags)?; let launcher_args = if backend_args.is_empty() { String::new() } else { @@ -1089,7 +1089,7 @@ fn make_self_extracting_dir( let hash = payload_hash(&payload)?; if windows { - let backend_args = format_backend_args_for_cmd(desktop_flags); + let backend_args = format_backend_args_for_cmd(desktop_flags)?; let launcher_args = if backend_args.is_empty() { String::new() } else { @@ -1108,7 +1108,7 @@ fn make_self_extracting_dir( ); std::fs::write(bundle_path.join(format!("{app_name}.bat")), launcher)?; } else { - let backend_args = format_backend_args_for_shell(desktop_flags); + let backend_args = format_backend_args_for_shell(desktop_flags)?; let launcher_args = if backend_args.is_empty() { String::new() } else { @@ -1259,43 +1259,24 @@ async fn spawn_framework_dev_server( Ok((url, child)) } -fn split_backend_args(backend_args: &str) -> Option> { - let mut candidates = vec![backend_args.to_string()]; - - if let Some(stripped) = backend_args - .strip_prefix('"') - .and_then(|s| s.strip_suffix('"')) - { - candidates.push(stripped.to_string()); - } else if let Some(stripped) = backend_args - .strip_prefix('\'') - .and_then(|s| s.strip_suffix('\'')) - { - candidates.push(stripped.to_string()); - } - - for candidate in candidates { - let normalized = candidate.replace(r#"\""#, "\"").replace(r"\'", "'"); - if let Some(tokens) = shlex::split(&normalized) { - return Some(tokens); - } - } - - None +/// Parses a raw `--backend-args` value into argv tokens using shell-style +/// quoting/escaping rules, so users can pass a single flag value containing +/// multiple backend args, some of which may themselves contain spaces (e.g. +/// `--backend-args '--user-agent="Custom Browser" --enable-x'`). +fn parse_backend_args(raw: &str) -> Result, AnyError> { + shell_words::split(raw).map_err(|e| { + deno_core::anyhow::anyhow!("invalid --backend-args quoting: {e}") + }) } -fn filtered_backend_args(desktop_flags: &DesktopFlags) -> Vec { +fn filtered_backend_args( + desktop_flags: &DesktopFlags, +) -> Result, AnyError> { let Some(backend_args) = desktop_flags.backend_args.as_deref() else { - return Vec::new(); + return Ok(Vec::new()); }; - let Some(tokens) = split_backend_args(backend_args) else { - log::warn!( - "Ignoring malformed backend args {:?}: could not parse shell syntax", - backend_args, - ); - return Vec::new(); - }; + let tokens = parse_backend_args(backend_args)?; let mut forwarded = Vec::new(); let mut iter = tokens.into_iter().peekable(); @@ -1318,41 +1299,50 @@ fn filtered_backend_args(desktop_flags: &DesktopFlags) -> Vec { } } - forwarded + Ok(forwarded) } fn apply_backend_flags( cmd: &mut std::process::Command, desktop_flags: &DesktopFlags, -) { - for token in filtered_backend_args(desktop_flags) { +) -> Result<(), AnyError> { + for token in filtered_backend_args(desktop_flags)? { cmd.arg(token); } + Ok(()) } -fn format_backend_args_for_shell(desktop_flags: &DesktopFlags) -> String { - filtered_backend_args(desktop_flags) - .into_iter() - .map(|arg| { - shlex::try_quote(&arg) - .unwrap_or_else(|_| { - std::borrow::Cow::Owned(format!("\"{}\"", arg.replace('"', "\\\""))) - }) - .into_owned() - }) - .collect::>() - .join(" ") +fn format_backend_args_for_shell( + desktop_flags: &DesktopFlags, +) -> Result { + Ok( + filtered_backend_args(desktop_flags)? + .into_iter() + .map(|arg| { + shlex::try_quote(&arg) + .unwrap_or_else(|_| { + std::borrow::Cow::Owned(format!("\"{}\"", arg.replace('"', "\\\""))) + }) + .into_owned() + }) + .collect::>() + .join(" "), + ) } -fn format_backend_args_for_cmd(desktop_flags: &DesktopFlags) -> String { - filtered_backend_args(desktop_flags) - .into_iter() - .map(|arg| { - let escaped = arg.replace('%', "%%").replace('"', "\"\""); - format!("\"{escaped}\"") - }) - .collect::>() - .join(" ") +fn format_backend_args_for_cmd( + desktop_flags: &DesktopFlags, +) -> Result { + Ok( + filtered_backend_args(desktop_flags)? + .into_iter() + .map(|arg| { + let escaped = arg.replace('%', "%%").replace('"', "\"\""); + format!("\"{escaped}\"") + }) + .collect::>() + .join(" "), + ) } /// Launch the desktop app with HMR enabled after compilation. @@ -1455,7 +1445,7 @@ async fn run_desktop_hmr( cmd.env("DENO_DESKTOP_HMR", &source_abs); } - apply_backend_flags(&mut cmd, desktop_flags); + apply_backend_flags(&mut cmd, desktop_flags)?; let _dev_server_child = if desktop_flags.hmr && let Some(fw) = framework @@ -1890,7 +1880,7 @@ async fn package_linux_app_dir( // the user runs directly. When backend args are present, we add a tiny shell // wrapper so those flags reach the backend while still pointing it at the // colocated runtime. - let backend_args = format_backend_args_for_shell(desktop_flags); + let backend_args = format_backend_args_for_shell(desktop_flags)?; let launcher_args = if backend_args.is_empty() { String::new() } else { @@ -3641,7 +3631,7 @@ fn create_linux_appimage( // AppRun is what the AppImage invokes on launch. Thin shell shim that // delegates to the existing launcher (which already sets $DIR and execs // the backend with the right args). - let backend_args = format_backend_args_for_shell(desktop_flags); + let backend_args = format_backend_args_for_shell(desktop_flags)?; let launcher_args = if backend_args.is_empty() { String::new() } else { @@ -7843,4 +7833,52 @@ def456 other.zip apply_desktop_config_to_flags(&mut flags, config); assert_eq!(flags.backend_args.as_deref(), None); } + + // --- parse_backend_args: shell-style tokenization of --backend-args --- + + #[test] + fn parse_backend_args_simple_quoted_value() { + let tokens = parse_backend_args(r#"--user-agent="potato""#).unwrap(); + assert_eq!(tokens, vec!["--user-agent=potato".to_string()]); + } + + #[test] + fn parse_backend_args_quoted_value_with_spaces() { + let tokens = + parse_backend_args(r#"--user-agent="potato browser" --enable-logging"#) + .unwrap(); + assert_eq!( + tokens, + vec![ + "--user-agent=potato browser".to_string(), + "--enable-logging".to_string(), + ] + ); + } + + #[test] + fn parse_backend_args_preserves_escaped_inner_quotes() { + let tokens = parse_backend_args(r#"--title="a \"quoted\" title""#).unwrap(); + assert_eq!(tokens, vec![r#"--title=a "quoted" title"#.to_string()]); + } + + #[test] + fn parse_backend_args_simple_unquoted_value_unaffected() { + let tokens = parse_backend_args("--enable-logging --verbose").unwrap(); + assert_eq!( + tokens, + vec!["--enable-logging".to_string(), "--verbose".to_string()] + ); + } + + #[test] + fn parse_backend_args_malformed_quoting_errors() { + let err = parse_backend_args(r#"--user-agent="unterminated"#).unwrap_err(); + assert!( + err + .to_string() + .starts_with("invalid --backend-args quoting: "), + "unexpected error message: {err}", + ); + } } diff --git a/tests/specs/desktop/backend_args/__test__.jsonc b/tests/specs/desktop/backend_args/__test__.jsonc index 94b22b4928c090..e617410dfc7941 100644 --- a/tests/specs/desktop/backend_args/__test__.jsonc +++ b/tests/specs/desktop/backend_args/__test__.jsonc @@ -8,7 +8,7 @@ "args": [ "desktop", "--backend=cef", - "--backend-args=\"--user-agent=\\\"potato\\\"\"", + "--backend-args=--user-agent=\"potato\"", "--output", "./hello", "./main.ts" @@ -31,7 +31,7 @@ "desktop", "--hmr", "--backend=cef", - "--backend-args=\"--user-agent=\\\"potato\\\"\"", + "--backend-args=--user-agent=\"potato\"", "--output", "./hello3", "./main.ts" @@ -40,6 +40,67 @@ } ] }, + "forwards_backend_flags_with_quoted_spaces": { + "steps": [ + { + "if": "unix", + "args": [ + "desktop", + "--backend=cef", + "--backend-args=--user-agent=\"potato browser\" --enable-logging", + "--output", + "./hello_spaces", + "./main.ts" + ], + "output": "[WILDCARD]" + }, + { + "if": "unix", + "commandName": "./hello_spaces/hello_spaces", + "args": [], + "output": "desktop_spaces.out" + } + ] + }, + "forwards_backend_flags_with_escaped_quotes": { + "steps": [ + { + "if": "unix", + "args": [ + "desktop", + "--backend=cef", + "--backend-args=--user-agent=\"a \\\"quoted\\\" agent\"", + "--output", + "./hello_escaped", + "./main.ts" + ], + "output": "[WILDCARD]" + }, + { + "if": "unix", + "commandName": "./hello_escaped/hello_escaped", + "args": [], + "output": "desktop_escaped.out" + } + ] + }, + "malformed_backend_args_reports_error": { + "steps": [ + { + "if": "unix", + "args": [ + "desktop", + "--backend=cef", + "--backend-args=--user-agent=\"unterminated", + "--output", + "./hello_malformed", + "./main.ts" + ], + "output": "[WILDCARD]invalid --backend-args quoting[WILDCARD]", + "exitCode": 1 + } + ] + }, "forwards_backend_flags_to_launcher_config": { "steps": [ { diff --git a/tests/specs/desktop/backend_args/desktop_escaped.out b/tests/specs/desktop/backend_args/desktop_escaped.out new file mode 100644 index 00000000000000..6f8a847f87c2cf --- /dev/null +++ b/tests/specs/desktop/backend_args/desktop_escaped.out @@ -0,0 +1,5 @@ +Runtime loaded successfully from: [WILDCARD]/hello_escaped/hello_escaped.so +Runtime started +[desktop] dylib path: "[WILDCARD]/hello_escaped/hello_escaped.so" +Listening on http://127.0.0.1:[WILDCARD]/ +a "quoted" agent diff --git a/tests/specs/desktop/backend_args/desktop_spaces.out b/tests/specs/desktop/backend_args/desktop_spaces.out new file mode 100644 index 00000000000000..802bd9c76b03e3 --- /dev/null +++ b/tests/specs/desktop/backend_args/desktop_spaces.out @@ -0,0 +1,5 @@ +Runtime loaded successfully from: [WILDCARD]/hello_spaces/hello_spaces.so +Runtime started +[desktop] dylib path: "[WILDCARD]/hello_spaces/hello_spaces.so" +Listening on http://127.0.0.1:[WILDCARD]/ +potato browser From f307647be97fb24a30772b41251d0d4bcfeec54c Mon Sep 17 00:00:00 2001 From: iownbey Date: Sat, 8 Aug 2026 17:34:31 -0700 Subject: [PATCH 09/25] use virtual desktop --- .github/workflows/ci.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.ts b/.github/workflows/ci.ts index 576c480df56305..8b20bedf001cae 100755 --- a/.github/workflows/ci.ts +++ b/.github/workflows/ci.ts @@ -1388,6 +1388,12 @@ const buildJobs = buildItems.map((rawBuildItem) => { }), }); const testCrateNameExpr = testMatrix.test_crate; + // Desktop spec tests (part of the "specs" crate) launch a real GUI + // backend, which needs a display. Linux CI runners have no display, so + // run the test binary under a virtual X display via xvfb-run. + const cargoTestCmdPrefix = rawBuildItem.os === "linux" + ? "xvfb-run -a " + : ""; const { restoreCacheStep, saveCacheStep, @@ -1534,8 +1540,9 @@ const buildJobs = buildItems.map((rawBuildItem) => { { name: "Test (debug)", if: isDebug, - run: - `cargo test -p ${testMatrix.test_package} --test ${testMatrix.test_crate}`, + // Desktop spec tests launch a GUI backend, so run under a virtual + // X display on Linux where no real display is available. + run: `${cargoTestCmdPrefix}cargo test -p ${testMatrix.test_package} --test ${testMatrix.test_crate}`, env: { CARGO_PROFILE_DEV_DEBUG: 0, CI_SHARD_INDEX: isPr.then(testMatrix.shard_index).else(""), @@ -1547,8 +1554,7 @@ const buildJobs = buildItems.map((rawBuildItem) => { if: isRelease.and( isDenoland.or(buildItem.use_sysroot), ), - run: - `cargo test -p ${testMatrix.test_package} --test ${testMatrix.test_crate} --release`, + run: `${cargoTestCmdPrefix}cargo test -p ${testMatrix.test_package} --test ${testMatrix.test_crate} --release`, env: { CI_SHARD_INDEX: isPr.then(testMatrix.shard_index).else(""), CI_SHARD_TOTAL: isPr.then(testMatrix.shard_total).else(""), From 7c3f1cde3993b7ec3339860d13c4e84643ed1356 Mon Sep 17 00:00:00 2001 From: iownbey Date: Sat, 8 Aug 2026 17:59:38 -0700 Subject: [PATCH 10/25] lint --- .github/workflows/ci.generated.yml | 8 ++-- .github/workflows/ci.ts | 6 ++- tools/deno.lock.json | 68 ++++++++++-------------------- 3 files changed, 30 insertions(+), 52 deletions(-) diff --git a/.github/workflows/ci.generated.yml b/.github/workflows/ci.generated.yml index de6f1813a88eaf..b81e8ba982ba0d 100644 --- a/.github/workflows/ci.generated.yml +++ b/.github/workflows/ci.generated.yml @@ -5178,7 +5178,7 @@ jobs: env: CI_SHARD_INDEX: '${{ github.event_name == ''pull_request'' && matrix.shard_index || '''' }}' CI_SHARD_TOTAL: '${{ github.event_name == ''pull_request'' && matrix.shard_total || '''' }}' - run: 'cargo test -p ${{ matrix.test_package }} --test ${{ matrix.test_crate }} --release' + run: 'xvfb-run -a cargo test -p ${{ matrix.test_package }} --test ${{ matrix.test_crate }} --release' - name: Ensure no git changes if: '!startsWith(github.ref, ''refs/tags/'') && github.event_name == ''pull_request''' run: |- @@ -5878,7 +5878,7 @@ jobs: CARGO_PROFILE_DEV_DEBUG: 0 CI_SHARD_INDEX: '${{ github.event_name == ''pull_request'' && matrix.shard_index || '''' }}' CI_SHARD_TOTAL: '${{ github.event_name == ''pull_request'' && matrix.shard_total || '''' }}' - run: 'cargo test -p ${{ matrix.test_package }} --test ${{ matrix.test_crate }}' + run: 'xvfb-run -a cargo test -p ${{ matrix.test_package }} --test ${{ matrix.test_crate }}' - name: Ensure no git changes if: '!startsWith(github.ref, ''refs/tags/'') && github.event_name == ''pull_request''' run: |- @@ -6393,7 +6393,7 @@ jobs: CARGO_PROFILE_DEV_DEBUG: 0 CI_SHARD_INDEX: '${{ github.event_name == ''pull_request'' && matrix.shard_index || '''' }}' CI_SHARD_TOTAL: '${{ github.event_name == ''pull_request'' && matrix.shard_total || '''' }}' - run: 'cargo test -p ${{ matrix.test_package }} --test ${{ matrix.test_crate }}' + run: 'xvfb-run -a cargo test -p ${{ matrix.test_package }} --test ${{ matrix.test_crate }}' - name: Ensure no git changes if: '!startsWith(github.ref, ''refs/tags/'') && github.event_name == ''pull_request''' run: |- @@ -7334,7 +7334,7 @@ jobs: env: CI_SHARD_INDEX: '${{ github.event_name == ''pull_request'' && matrix.shard_index || '''' }}' CI_SHARD_TOTAL: '${{ github.event_name == ''pull_request'' && matrix.shard_total || '''' }}' - run: 'cargo test -p ${{ matrix.test_package }} --test ${{ matrix.test_crate }} --release' + run: 'xvfb-run -a cargo test -p ${{ matrix.test_package }} --test ${{ matrix.test_crate }} --release' - name: Ensure no git changes if: '!startsWith(github.ref, ''refs/tags/'') && !(!contains(github.event.pull_request.labels.*.name, ''ci-full'') && github.event_name == ''pull_request'') && github.event_name == ''pull_request''' run: |- diff --git a/.github/workflows/ci.ts b/.github/workflows/ci.ts index 8b20bedf001cae..078273f56cdf5a 100755 --- a/.github/workflows/ci.ts +++ b/.github/workflows/ci.ts @@ -1542,7 +1542,8 @@ const buildJobs = buildItems.map((rawBuildItem) => { if: isDebug, // Desktop spec tests launch a GUI backend, so run under a virtual // X display on Linux where no real display is available. - run: `${cargoTestCmdPrefix}cargo test -p ${testMatrix.test_package} --test ${testMatrix.test_crate}`, + run: + `${cargoTestCmdPrefix}cargo test -p ${testMatrix.test_package} --test ${testMatrix.test_crate}`, env: { CARGO_PROFILE_DEV_DEBUG: 0, CI_SHARD_INDEX: isPr.then(testMatrix.shard_index).else(""), @@ -1554,7 +1555,8 @@ const buildJobs = buildItems.map((rawBuildItem) => { if: isRelease.and( isDenoland.or(buildItem.use_sysroot), ), - run: `${cargoTestCmdPrefix}cargo test -p ${testMatrix.test_package} --test ${testMatrix.test_crate} --release`, + run: + `${cargoTestCmdPrefix}cargo test -p ${testMatrix.test_package} --test ${testMatrix.test_crate} --release`, env: { CI_SHARD_INDEX: isPr.then(testMatrix.shard_index).else(""), CI_SHARD_TOTAL: isPr.then(testMatrix.shard_total).else(""), diff --git a/tools/deno.lock.json b/tools/deno.lock.json index 9dd838ae904bab..571e55f037dc02 100644 --- a/tools/deno.lock.json +++ b/tools/deno.lock.json @@ -4,9 +4,13 @@ "jsr:@b-fuze/deno-dom@0.1.56": "0.1.56", "jsr:@david/console-static-text@0.3": "0.3.4", "jsr:@david/dax@~0.43.2": "0.43.2", + "jsr:@david/gagen@0.3.1": "0.3.1", "jsr:@david/path@0.2": "0.2.0", "jsr:@david/which@~0.4.1": "0.4.2", "jsr:@deno/rust-automation@0.22.2": "0.22.2", + "jsr:@std/collections@^1.1.3": "1.3.0", + "jsr:@std/toml@1": "1.0.11", + "jsr:@std/yaml@^1.0.11": "1.1.2", "npm:decompress@4.2.1": "4.2.1", "npm:octokit@^5.0.3": "5.0.5" }, @@ -25,6 +29,12 @@ "jsr:@david/which" ] }, + "@david/gagen@0.3.1": { + "integrity": "888b90d356e6530f24d9c9ec5beff1f0d0565d44293b356cd6844768ffe31780", + "dependencies": [ + "jsr:@std/yaml" + ] + }, "@david/path@0.2.0": { "integrity": "f2d7aa7f02ce5a55e27c09f9f1381794acb09d328f8d3c8a2e3ab3ffc294dccd" }, @@ -37,6 +47,18 @@ "jsr:@david/dax", "npm:octokit" ] + }, + "@std/collections@1.3.0": { + "integrity": "eb36b43d784477ea0b476483ac034a14bdd182aff921c812ecf662a1fcef9498" + }, + "@std/toml@1.0.11": { + "integrity": "e084988b872ca4bad6aedfb7350f6eeed0e8ba88e9ee5e1590621c5b5bb8f715", + "dependencies": [ + "jsr:@std/collections" + ] + }, + "@std/yaml@1.1.2": { + "integrity": "de612653c036749ba2044165e316f52412b0f0698afffb7ee80b5331d6d2abae" } }, "npm": { @@ -689,51 +711,5 @@ "fd-slicer" ] } - }, - "workspace": { - "links": { - "jsr:@std/assert@1.0.19": {}, - "jsr:@std/async@1.2.0": {}, - "jsr:@std/bytes@1.0.6": {}, - "jsr:@std/cache@0.2.2": {}, - "jsr:@std/cbor@0.1.9": {}, - "jsr:@std/cli@1.0.28": {}, - "jsr:@std/collections@1.1.6": {}, - "jsr:@std/crypto@1.0.5": {}, - "jsr:@std/csv@1.0.6": {}, - "jsr:@std/data-structures@1.0.10": {}, - "jsr:@std/datetime@0.225.7": {}, - "jsr:@std/dotenv@0.225.6": {}, - "jsr:@std/encoding@1.0.10": {}, - "jsr:@std/expect@1.0.18": {}, - "jsr:@std/fmt@1.0.9": {}, - "jsr:@std/front-matter@1.0.9": {}, - "jsr:@std/fs@1.0.23": {}, - "jsr:@std/html@1.0.5": {}, - "jsr:@std/http@1.0.25": {}, - "jsr:@std/ini@1.0.0-rc.9": {}, - "jsr:@std/internal@1.0.12": {}, - "jsr:@std/io@0.225.3": {}, - "jsr:@std/json@1.0.3": {}, - "jsr:@std/jsonc@1.0.2": {}, - "jsr:@std/math@0.0.0": {}, - "jsr:@std/media-types@1.1.0": {}, - "jsr:@std/msgpack@1.0.3": {}, - "jsr:@std/net@1.0.6": {}, - "jsr:@std/path@1.1.4": {}, - "jsr:@std/random@0.1.5": {}, - "jsr:@std/regexp@1.0.1": {}, - "jsr:@std/semver@1.0.8": {}, - "jsr:@std/streams@1.0.17": {}, - "jsr:@std/tar@0.1.10": {}, - "jsr:@std/testing@1.0.17": {}, - "jsr:@std/text@1.0.17": {}, - "jsr:@std/toml@1.0.11": {}, - "jsr:@std/ulid@1.0.0": {}, - "jsr:@std/uuid@1.1.0": {}, - "jsr:@std/webgpu@0.224.9": {}, - "jsr:@std/xml@0.1.0": {}, - "jsr:@std/yaml@1.0.12": {} - } } } From 37cb8e04970ab45abb1425231a83ad093c827d89 Mon Sep 17 00:00:00 2001 From: iownbey <54186227+iownbey@users.noreply.github.com> Date: Sat, 8 Aug 2026 21:53:39 -0700 Subject: [PATCH 11/25] Gate backend_args launcher tests to Linux, add macOS packaging tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The existing tests assert the Linux app-dir layout (.//), which macOS doesn't produce — macOS packages a .app bundle instead, and its packaging path doesn't consume --backend-args at all (backend args only reach the HMR launch path and the Linux/Windows/self-extracting launchers). - Gate launcher + malformed-args compile tests to "linux" - Cover malformed-args on macOS via the --hmr path, which does parse them - Add macOS tests asserting the .app bundle is packaged successfully (CLI flag and deno.json config variants) --- .../specs/desktop/backend_args/__test__.jsonc | 64 ++++++++++++++++--- 1 file changed, 55 insertions(+), 9 deletions(-) diff --git a/tests/specs/desktop/backend_args/__test__.jsonc b/tests/specs/desktop/backend_args/__test__.jsonc index e617410dfc7941..3a41f0dfc3b817 100644 --- a/tests/specs/desktop/backend_args/__test__.jsonc +++ b/tests/specs/desktop/backend_args/__test__.jsonc @@ -4,7 +4,7 @@ "forwards_backend_flags_to_launcher": { "steps": [ { - "if": "unix", + "if": "linux", "args": [ "desktop", "--backend=cef", @@ -16,7 +16,7 @@ "output": "[WILDCARD]" }, { - "if": "unix", + "if": "linux", "commandName": "./hello/hello", "args": [], "output": "desktop.out" @@ -43,7 +43,7 @@ "forwards_backend_flags_with_quoted_spaces": { "steps": [ { - "if": "unix", + "if": "linux", "args": [ "desktop", "--backend=cef", @@ -55,7 +55,7 @@ "output": "[WILDCARD]" }, { - "if": "unix", + "if": "linux", "commandName": "./hello_spaces/hello_spaces", "args": [], "output": "desktop_spaces.out" @@ -65,7 +65,7 @@ "forwards_backend_flags_with_escaped_quotes": { "steps": [ { - "if": "unix", + "if": "linux", "args": [ "desktop", "--backend=cef", @@ -77,7 +77,7 @@ "output": "[WILDCARD]" }, { - "if": "unix", + "if": "linux", "commandName": "./hello_escaped/hello_escaped", "args": [], "output": "desktop_escaped.out" @@ -87,9 +87,23 @@ "malformed_backend_args_reports_error": { "steps": [ { - "if": "unix", + "if": "linux", + "args": [ + "desktop", + "--backend=cef", + "--backend-args=--user-agent=\"unterminated", + "--output", + "./hello_malformed", + "./main.ts" + ], + "output": "[WILDCARD]invalid --backend-args quoting[WILDCARD]", + "exitCode": 1 + }, + { + "if": "mac", "args": [ "desktop", + "--hmr", "--backend=cef", "--backend-args=--user-agent=\"unterminated", "--output", @@ -104,7 +118,7 @@ "forwards_backend_flags_to_launcher_config": { "steps": [ { - "if": "unix", + "if": "linux", "args": [ "desktop", "--output", @@ -116,12 +130,44 @@ "output": "[WILDCARD]" }, { - "if": "unix", + "if": "linux", "commandName": "./hello2/hello2", "args": [], "output": "desktop_config.out" } ] + }, + "packages_macos_app_bundle": { + "steps": [ + { + "if": "mac", + "args": [ + "desktop", + "--backend=cef", + "--backend-args=--user-agent=\"potato\"", + "--output", + "./hello", + "./main.ts" + ], + "output": "desktop_mac.out" + } + ] + }, + "packages_macos_app_bundle_config": { + "steps": [ + { + "if": "mac", + "args": [ + "desktop", + "--output", + "./hello2", + "--config", + "./desktop_config.jsonc", + "./main.ts" + ], + "output": "desktop_mac.out" + } + ] } } } From 1d3eef9e069165caa4a94618c33257a4b7a85ac7 Mon Sep 17 00:00:00 2001 From: iownbey <54186227+iownbey@users.noreply.github.com> Date: Sat, 8 Aug 2026 21:55:55 -0700 Subject: [PATCH 12/25] Add expected output fixture for macOS backend_args packaging test --- tests/specs/desktop/backend_args/desktop_mac.out | 1 + 1 file changed, 1 insertion(+) create mode 100644 tests/specs/desktop/backend_args/desktop_mac.out diff --git a/tests/specs/desktop/backend_args/desktop_mac.out b/tests/specs/desktop/backend_args/desktop_mac.out new file mode 100644 index 00000000000000..e3efb696823d53 --- /dev/null +++ b/tests/specs/desktop/backend_args/desktop_mac.out @@ -0,0 +1 @@ +[WILDCARD]Bundle[WILDCARD]hello.app[WILDCARD] From 9d26e23e3274426135355f33d816e756a1431124 Mon Sep 17 00:00:00 2001 From: iownbey <54186227+iownbey@users.noreply.github.com> Date: Sat, 8 Aug 2026 21:58:17 -0700 Subject: [PATCH 13/25] Add expected output fixture for macOS config-driven packaging test --- tests/specs/desktop/backend_args/desktop_mac_config.out | 1 + 1 file changed, 1 insertion(+) create mode 100644 tests/specs/desktop/backend_args/desktop_mac_config.out diff --git a/tests/specs/desktop/backend_args/desktop_mac_config.out b/tests/specs/desktop/backend_args/desktop_mac_config.out new file mode 100644 index 00000000000000..9bf31f137ffc4e --- /dev/null +++ b/tests/specs/desktop/backend_args/desktop_mac_config.out @@ -0,0 +1 @@ +[WILDCARD]Bundle[WILDCARD]hello2.app[WILDCARD] From 8a71e7adc1eaa3aa0cd6db603003ced9ce65c655 Mon Sep 17 00:00:00 2001 From: iownbey <54186227+iownbey@users.noreply.github.com> Date: Sat, 8 Aug 2026 22:00:08 -0700 Subject: [PATCH 14/25] Point macOS config packaging test at its own fixture MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit packages_macos_app_bundle_config compiles ./hello2, so its Bundle line says hello2.app — it can't share desktop_mac.out (hello.app). --- tests/specs/desktop/backend_args/__test__.jsonc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/specs/desktop/backend_args/__test__.jsonc b/tests/specs/desktop/backend_args/__test__.jsonc index 3a41f0dfc3b817..55bb0a59413f9a 100644 --- a/tests/specs/desktop/backend_args/__test__.jsonc +++ b/tests/specs/desktop/backend_args/__test__.jsonc @@ -165,7 +165,7 @@ "./desktop_config.jsonc", "./main.ts" ], - "output": "desktop_mac.out" + "output": "desktop_mac_config.out" } ] } From 48f858c455d9c9db245ca48867f4d524520acb7d Mon Sep 17 00:00:00 2001 From: iownbey <54186227+iownbey@users.noreply.github.com> Date: Sat, 8 Aug 2026 22:56:27 -0700 Subject: [PATCH 15/25] Default tools/download_laufey.ts to webview backend on Windows laufey v0.6.1 does not publish a cef build for aarch64-pc-windows-msvc, so the "Pre-download native laufey" CI step always fails on the windows aarch64 legs. Default to the webview backend on Windows (both x86_64 and aarch64 pins exist for it). --- tools/download_laufey.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tools/download_laufey.ts b/tools/download_laufey.ts index 7a30d23cd0280a..257bfdbacaee68 100644 --- a/tools/download_laufey.ts +++ b/tools/download_laufey.ts @@ -51,7 +51,14 @@ function resolveDenoBin(): string { ); } -const backend = Deno.args[0] ?? "cef"; +// laufey v0.6.1 does not ship a `cef` build for aarch64-pc-windows-msvc +// (cli/laufey_sums.lock intentionally has no pin for it), so Windows CI +// legs warm the cache with the `webview` backend instead. +function defaultBackend(): string { + return Deno.build.os === "windows" ? "webview" : "cef"; +} + +const backend = Deno.args[0] ?? defaultBackend(); const cacheDir = absolute(Deno.args[1] ?? "./target/.native_laufey"); const denoBin = resolveDenoBin(); From f146ebd731892046b6dd52876b5b0f4ffab61903 Mon Sep 17 00:00:00 2001 From: iownbey <54186227+iownbey@users.noreply.github.com> Date: Sat, 8 Aug 2026 22:59:26 -0700 Subject: [PATCH 16/25] Add Windows spec tests for desktop backend args (webview) laufey v0.6.1 has no cef build for aarch64-pc-windows-msvc, so the Windows variants exercise the webview backend (which is published and pinned for both Windows targets). Covers flag forwarding, quoted spaces, escaped quotes, malformed args, and config-file backendArgs. --- .../specs/desktop/backend_args/__test__.jsonc | 89 +++++++++++++++++++ .../backend_args/desktop_config_windows.jsonc | 8 ++ .../backend_args/desktop_config_windows.out | 2 + .../backend_args/desktop_escaped_windows.out | 2 + .../backend_args/desktop_spaces_windows.out | 2 + .../desktop/backend_args/desktop_windows.out | 2 + 6 files changed, 105 insertions(+) create mode 100644 tests/specs/desktop/backend_args/desktop_config_windows.jsonc create mode 100644 tests/specs/desktop/backend_args/desktop_config_windows.out create mode 100644 tests/specs/desktop/backend_args/desktop_escaped_windows.out create mode 100644 tests/specs/desktop/backend_args/desktop_spaces_windows.out create mode 100644 tests/specs/desktop/backend_args/desktop_windows.out diff --git a/tests/specs/desktop/backend_args/__test__.jsonc b/tests/specs/desktop/backend_args/__test__.jsonc index 55bb0a59413f9a..ba221000f329df 100644 --- a/tests/specs/desktop/backend_args/__test__.jsonc +++ b/tests/specs/desktop/backend_args/__test__.jsonc @@ -20,6 +20,26 @@ "commandName": "./hello/hello", "args": [], "output": "desktop.out" + }, + { + // laufey v0.6.1 ships no cef build for aarch64-pc-windows-msvc, + // so Windows exercises the webview backend instead. + "if": "windows", + "args": [ + "desktop", + "--backend=webview", + "--backend-args=--user-agent=\"potato\"", + "--output", + "./hello", + "./main.ts" + ], + "output": "[WILDCARD]" + }, + { + "if": "windows", + "commandName": "./hello/hello.exe", + "args": [], + "output": "desktop_windows.out" } ] }, @@ -59,6 +79,24 @@ "commandName": "./hello_spaces/hello_spaces", "args": [], "output": "desktop_spaces.out" + }, + { + "if": "windows", + "args": [ + "desktop", + "--backend=webview", + "--backend-args=--user-agent=\"potato browser\" --enable-logging", + "--output", + "./hello_spaces", + "./main.ts" + ], + "output": "[WILDCARD]" + }, + { + "if": "windows", + "commandName": "./hello_spaces/hello_spaces.exe", + "args": [], + "output": "desktop_spaces_windows.out" } ] }, @@ -81,6 +119,24 @@ "commandName": "./hello_escaped/hello_escaped", "args": [], "output": "desktop_escaped.out" + }, + { + "if": "windows", + "args": [ + "desktop", + "--backend=webview", + "--backend-args=--user-agent=\"a \\\"quoted\\\" agent\"", + "--output", + "./hello_escaped", + "./main.ts" + ], + "output": "[WILDCARD]" + }, + { + "if": "windows", + "commandName": "./hello_escaped/hello_escaped.exe", + "args": [], + "output": "desktop_escaped_windows.out" } ] }, @@ -112,6 +168,21 @@ ], "output": "[WILDCARD]invalid --backend-args quoting[WILDCARD]", "exitCode": 1 + }, + { + // Quoting is parsed before the backend downloads, so the failure + // must reproduce on the webview backend Windows uses. + "if": "windows", + "args": [ + "desktop", + "--backend=webview", + "--backend-args=--user-agent=\"unterminated", + "--output", + "./hello_malformed", + "./main.ts" + ], + "output": "[WILDCARD]invalid --backend-args quoting[WILDCARD]", + "exitCode": 1 } ] }, @@ -134,6 +205,24 @@ "commandName": "./hello2/hello2", "args": [], "output": "desktop_config.out" + }, + { + "if": "windows", + "args": [ + "desktop", + "--output", + "./hello2", + "--config", + "./desktop_config_windows.jsonc", + "./main.ts" + ], + "output": "[WILDCARD]" + }, + { + "if": "windows", + "commandName": "./hello2/hello2.exe", + "args": [], + "output": "desktop_config_windows.out" } ] }, diff --git a/tests/specs/desktop/backend_args/desktop_config_windows.jsonc b/tests/specs/desktop/backend_args/desktop_config_windows.jsonc new file mode 100644 index 00000000000000..10401674e9c409 --- /dev/null +++ b/tests/specs/desktop/backend_args/desktop_config_windows.jsonc @@ -0,0 +1,8 @@ +{ + "desktop": { + "backend": "webview", + "backendArgs": { + "webview": "--user-agent=potato" + } + } +} diff --git a/tests/specs/desktop/backend_args/desktop_config_windows.out b/tests/specs/desktop/backend_args/desktop_config_windows.out new file mode 100644 index 00000000000000..cd2dea3941c009 --- /dev/null +++ b/tests/specs/desktop/backend_args/desktop_config_windows.out @@ -0,0 +1,2 @@ +[WILDCARD] +potato diff --git a/tests/specs/desktop/backend_args/desktop_escaped_windows.out b/tests/specs/desktop/backend_args/desktop_escaped_windows.out new file mode 100644 index 00000000000000..c395e524c17d04 --- /dev/null +++ b/tests/specs/desktop/backend_args/desktop_escaped_windows.out @@ -0,0 +1,2 @@ +[WILDCARD] +a "quoted" agent diff --git a/tests/specs/desktop/backend_args/desktop_spaces_windows.out b/tests/specs/desktop/backend_args/desktop_spaces_windows.out new file mode 100644 index 00000000000000..9c93b47c0c5e38 --- /dev/null +++ b/tests/specs/desktop/backend_args/desktop_spaces_windows.out @@ -0,0 +1,2 @@ +[WILDCARD] +potato browser diff --git a/tests/specs/desktop/backend_args/desktop_windows.out b/tests/specs/desktop/backend_args/desktop_windows.out new file mode 100644 index 00000000000000..cd2dea3941c009 --- /dev/null +++ b/tests/specs/desktop/backend_args/desktop_windows.out @@ -0,0 +1,2 @@ +[WILDCARD] +potato From 2d500eb7e28f6ee0f070fa4e06f484ba19377de8 Mon Sep 17 00:00:00 2001 From: iownbey <54186227+iownbey@users.noreply.github.com> Date: Sat, 8 Aug 2026 23:49:44 -0700 Subject: [PATCH 17/25] Windows desktop spec tests: assert packaging only, never launch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The laufey webview backend (all Windows tests use it — v0.6.1 has no cef aarch64-pc-windows-msvc build) parses only --runtime from argv and silently drops everything else; unlike cef, which feeds the process argv into CefMainArgs so extra switches (e.g. --user-agent) reach Chromium. Launching the packaged app on Windows therefore never makes the user-agent request the fixture's main.ts waits for, and the test hangs forever. Drop the exe-launch steps on Windows; packaging alone still exercises --backend-args parsing, quoting, and baking into the launcher. The *_windows.out launch fixtures are unused after this — remove them separately (no API delete available here). --- .../specs/desktop/backend_args/__test__.jsonc | 39 +++++++------------ 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/tests/specs/desktop/backend_args/__test__.jsonc b/tests/specs/desktop/backend_args/__test__.jsonc index ba221000f329df..968400b5b5ece8 100644 --- a/tests/specs/desktop/backend_args/__test__.jsonc +++ b/tests/specs/desktop/backend_args/__test__.jsonc @@ -23,7 +23,14 @@ }, { // laufey v0.6.1 ships no cef build for aarch64-pc-windows-msvc, - // so Windows exercises the webview backend instead. + // so Windows exercises the webview backend instead. Unlike the + // cef backend (which feeds the process argv into CefMainArgs so + // extra switches reach Chromium), the webview backend parses only + // `--runtime` and silently drops everything else — so the packaged + // app's webview would keep its default user agent and the app's + // `main.ts` would never see a user-agent header and exit. Only + // assert packaging (which is where backend args are parsed and + // baked into the launcher), never launch the packaged app here. "if": "windows", "args": [ "desktop", @@ -34,12 +41,6 @@ "./main.ts" ], "output": "[WILDCARD]" - }, - { - "if": "windows", - "commandName": "./hello/hello.exe", - "args": [], - "output": "desktop_windows.out" } ] }, @@ -81,6 +82,8 @@ "output": "desktop_spaces.out" }, { + // Windows (webview): packaging only, see + // forwards_backend_flags_to_launcher. "if": "windows", "args": [ "desktop", @@ -91,12 +94,6 @@ "./main.ts" ], "output": "[WILDCARD]" - }, - { - "if": "windows", - "commandName": "./hello_spaces/hello_spaces.exe", - "args": [], - "output": "desktop_spaces_windows.out" } ] }, @@ -121,6 +118,8 @@ "output": "desktop_escaped.out" }, { + // Windows (webview): packaging only, see + // forwards_backend_flags_to_launcher. "if": "windows", "args": [ "desktop", @@ -131,12 +130,6 @@ "./main.ts" ], "output": "[WILDCARD]" - }, - { - "if": "windows", - "commandName": "./hello_escaped/hello_escaped.exe", - "args": [], - "output": "desktop_escaped_windows.out" } ] }, @@ -207,6 +200,8 @@ "output": "desktop_config.out" }, { + // Windows (webview): packaging only, see + // forwards_backend_flags_to_launcher. "if": "windows", "args": [ "desktop", @@ -217,12 +212,6 @@ "./main.ts" ], "output": "[WILDCARD]" - }, - { - "if": "windows", - "commandName": "./hello2/hello2.exe", - "args": [], - "output": "desktop_config_windows.out" } ] }, From de93e5f212ec39d5cbc0736196cd0cfc681e0541 Mon Sep 17 00:00:00 2001 From: iownbey Date: Sat, 8 Aug 2026 23:56:41 -0700 Subject: [PATCH 18/25] Remove unused windows launch fixtures --- tests/specs/desktop/backend_args/desktop_config_windows.out | 2 -- tests/specs/desktop/backend_args/desktop_escaped_windows.out | 2 -- tests/specs/desktop/backend_args/desktop_spaces_windows.out | 2 -- tests/specs/desktop/backend_args/desktop_windows.out | 2 -- 4 files changed, 8 deletions(-) delete mode 100644 tests/specs/desktop/backend_args/desktop_config_windows.out delete mode 100644 tests/specs/desktop/backend_args/desktop_escaped_windows.out delete mode 100644 tests/specs/desktop/backend_args/desktop_spaces_windows.out delete mode 100644 tests/specs/desktop/backend_args/desktop_windows.out diff --git a/tests/specs/desktop/backend_args/desktop_config_windows.out b/tests/specs/desktop/backend_args/desktop_config_windows.out deleted file mode 100644 index cd2dea3941c009..00000000000000 --- a/tests/specs/desktop/backend_args/desktop_config_windows.out +++ /dev/null @@ -1,2 +0,0 @@ -[WILDCARD] -potato diff --git a/tests/specs/desktop/backend_args/desktop_escaped_windows.out b/tests/specs/desktop/backend_args/desktop_escaped_windows.out deleted file mode 100644 index c395e524c17d04..00000000000000 --- a/tests/specs/desktop/backend_args/desktop_escaped_windows.out +++ /dev/null @@ -1,2 +0,0 @@ -[WILDCARD] -a "quoted" agent diff --git a/tests/specs/desktop/backend_args/desktop_spaces_windows.out b/tests/specs/desktop/backend_args/desktop_spaces_windows.out deleted file mode 100644 index 9c93b47c0c5e38..00000000000000 --- a/tests/specs/desktop/backend_args/desktop_spaces_windows.out +++ /dev/null @@ -1,2 +0,0 @@ -[WILDCARD] -potato browser diff --git a/tests/specs/desktop/backend_args/desktop_windows.out b/tests/specs/desktop/backend_args/desktop_windows.out deleted file mode 100644 index cd2dea3941c009..00000000000000 --- a/tests/specs/desktop/backend_args/desktop_windows.out +++ /dev/null @@ -1,2 +0,0 @@ -[WILDCARD] -potato From 1748999c36670e1de5021fbce4df34a1b3ad007f Mon Sep 17 00:00:00 2001 From: iownbey <54186227+iownbey@users.noreply.github.com> Date: Sun, 9 Aug 2026 00:23:46 -0700 Subject: [PATCH 19/25] Fix malformed_backend_args windows step: fail fast with --hmr The windows (webview) packaging path reports the quoting error too late and with different surrounding output than the non-hmr variants, so the output pattern never matched. Mirror the mac variant: --hmr surfaces the quoting error at dev-server startup, before any backend download. --- tests/specs/desktop/backend_args/__test__.jsonc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/specs/desktop/backend_args/__test__.jsonc b/tests/specs/desktop/backend_args/__test__.jsonc index 968400b5b5ece8..42f863581428eb 100644 --- a/tests/specs/desktop/backend_args/__test__.jsonc +++ b/tests/specs/desktop/backend_args/__test__.jsonc @@ -163,11 +163,12 @@ "exitCode": 1 }, { - // Quoting is parsed before the backend downloads, so the failure - // must reproduce on the webview backend Windows uses. + // Mirror the mac variant: --hmr makes the malformed quoting fail + // fast at dev-server startup, before any backend download/launch. "if": "windows", "args": [ "desktop", + "--hmr", "--backend=webview", "--backend-args=--user-agent=\"unterminated", "--output", From 4b5603c856e88decef489f5942ca9483860cbd37 Mon Sep 17 00:00:00 2001 From: iownbey Date: Sun, 9 Aug 2026 01:00:05 -0700 Subject: [PATCH 20/25] submodule revert --- tests/node_compat/runner/suite | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/node_compat/runner/suite b/tests/node_compat/runner/suite index 195065d326100e..f287abd8976855 160000 --- a/tests/node_compat/runner/suite +++ b/tests/node_compat/runner/suite @@ -1 +1 @@ -Subproject commit 195065d326100e6fabbd9f90ffe09d0f19cb97f6 +Subproject commit f287abd897685505b021996828004a917f715a0f From 1d5dbb05d953c5a8594c8b7afe4bf8adb3d0dd64 Mon Sep 17 00:00:00 2001 From: iownbey <54186227+iownbey@users.noreply.github.com> Date: Sun, 9 Aug 2026 01:51:54 -0700 Subject: [PATCH 21/25] test(desktop): add 60s per-step timeout to backend_args spec tests Steps that launch a packaged desktop app only exit when the GUI backend makes its HTTP request to the test server. When the backend can't start on a CI runner (no usable display/sandbox), the process hangs until the whole job is cancelled. Set a 60s timeout on every step so the harness kills the process and fails the test with output instead of stalling. --- tests/specs/desktop/backend_args/__test__.jsonc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/specs/desktop/backend_args/__test__.jsonc b/tests/specs/desktop/backend_args/__test__.jsonc index 42f863581428eb..6100bef7914245 100644 --- a/tests/specs/desktop/backend_args/__test__.jsonc +++ b/tests/specs/desktop/backend_args/__test__.jsonc @@ -1,5 +1,11 @@ { "tempDir": true, + // Kill any step that doesn't finish in 60s instead of hanging until CI + // cancels the job. The steps that launch a packaged app only exit when the + // GUI backend makes its HTTP request; if the backend can't start (e.g. no + // usable display/sandbox on a CI runner) the process would otherwise run + // forever. + "timeout": 60, "tests": { "forwards_backend_flags_to_launcher": { "steps": [ From a2c1759295187c62a7c705074fc8a7811a0fd94e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 9 Aug 2026 18:45:18 +0000 Subject: [PATCH 22/25] Initial plan From 729692a07d01c4db421fa50d8312312d106f3a41 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 9 Aug 2026 19:01:25 +0000 Subject: [PATCH 23/25] fix(desktop): switch laufey predownload to direct archive fetch Co-authored-by: iownbey <54186227+iownbey@users.noreply.github.com> --- tools/download_laufey.ts | 350 ++++++++++++++++++++++++++-------- tools/download_laufey_test.ts | 121 ++++++++++++ 2 files changed, 393 insertions(+), 78 deletions(-) create mode 100644 tools/download_laufey_test.ts diff --git a/tools/download_laufey.ts b/tools/download_laufey.ts index 257bfdbacaee68..9237983df3d84f 100644 --- a/tools/download_laufey.ts +++ b/tools/download_laufey.ts @@ -1,93 +1,287 @@ -#!/usr/bin/env -S deno run --allow-read --allow-write --allow-env --allow-run --allow-net +#!/usr/bin/env -S deno run --allow-read --allow-write --allow-net --allow-run // Copyright 2018-2026 the Deno authors. MIT license. // deno-lint-ignore-file no-console -// Pre-downloads a "laufey" desktop backend that `deno desktop --backend=` -// launches, into a cache directory, so CI (and local test runs) don't -// re-download the (100+ MB) backend archive for every test's fresh -// `DENO_DIR`. -// -// It warms the cache by compiling (not running) a trivial desktop app with -// the built `deno` binary, which downloads + packages the backend via its -// normal path. The test harness points every spec test at the same cache -// directory via `DENO_LAUFEY_CACHE_DIR` (see -// `test_util::native_laufey_cache_dir`), so this script only needs to warm -// the cache - it does not export the path into the env. +// Pre-downloads a pinned laufey desktop backend archive into the shared cache +// layout `deno desktop` expects, so CI (and local test runs) don't re-download +// the backend for every test's fresh `DENO_DIR`. // // deno run -A tools/download_laufey.ts [backend] [cache_dir] // -// For local `cargo test` runs, running it once (with the default backend -// and cache directory) is enough; the harness points every test at -// `target/.native_laufey`. To point tests at a different cache instead, -// export it explicitly: -// -// export DENO_LAUFEY_CACHE_DIR=/path/to/cache -// -// The `deno` binary to use is taken from the `DENO_BIN` env var, else the -// first of `./target/release/deno` or `./target/debug/deno` that exists. +// The default backend stays `cef`, except on Windows where it stays `webview` +// because laufey v0.6.1 does not ship a `cef` build for +// `aarch64-pc-windows-msvc`. -const exe = Deno.build.os === "windows" ? ".exe" : ""; +const LAUFEY_SUMS_URL = new URL("../cli/laufey_sums.lock", import.meta.url); -// Absolute paths are required because the download runs the compiler in a -// different working directory, against which relative paths would resolve. -function absolute(p: string): string { - if (p.startsWith("/") || /^[A-Za-z]:[\\/]/.test(p)) return p; - return `${Deno.cwd()}/${p}`; +function absolute(path: string): string { + if (path.startsWith("/") || /^[A-Za-z]:[\\/]/.test(path)) { + return path; + } + return `${Deno.cwd()}/${path}`; } -function resolveDenoBin(): string { - const fromEnv = Deno.env.get("DENO_BIN"); - if (fromEnv) return Deno.realPathSync(fromEnv); - for (const profile of ["release", "debug"]) { - const candidate = `./target/${profile}/deno${exe}`; - try { - return Deno.realPathSync(candidate); - } catch { - // try the next profile +async function exists(path: string): Promise { + try { + await Deno.stat(path); + return true; + } catch (error) { + if (error instanceof Deno.errors.NotFound) { + return false; + } + throw error; + } +} + +export function defaultBackendFor(os: string): string { + return os === "windows" ? "webview" : "cef"; +} + +export function parsePinnedVersion(contents: string): string { + const match = contents.match(/^# version: v([0-9][^\s]*)$/m); + if (match == null) { + throw new Error( + "cli/laufey_sums.lock is missing a '# version: vX.Y.Z' directive", + ); + } + return match[1]; +} + +export function parsePinnedSha256( + contents: string, + archive: string, +): string | null { + for (const line of contents.split("\n")) { + const match = line.match(/^([0-9a-fA-F]+)\s+\*?(\S+)$/); + if (match?.[2] === archive) { + return match[1]; + } + } + return null; +} + +export function laufeyTargetForBuild( + build: Pick, +): string { + const arch = build.arch; + if (arch !== "x86_64" && arch !== "aarch64") { + throw new Error(`unsupported laufey architecture: ${arch}`); + } + + const os = (() => { + switch (build.os) { + case "linux": + return "unknown-linux-gnu"; + case "darwin": + return "apple-darwin"; + case "windows": + return "pc-windows-msvc"; + default: + throw new Error(`unsupported laufey operating system: ${build.os}`); } + })(); + + return `${arch}-${os}`; +} + +export function laufeyArchiveName(backend: string, target: string): string { + const archiveBackend = backend === "raw" ? "winit" : backend; + const extension = target.includes("windows") ? "zip" : "tar.gz"; + return `laufey-${archiveBackend}-${target}.${extension}`; +} + +export function laufeyReleaseUrl(version: string, archive: string): string { + return `https://github.com/littledivy/laufey/releases/download/v${version}/${archive}`; +} + +type DownloadPlan = { + archive: string; + markerPath: string; + parentDir: string; + sha256: string; + target: string; + targetDir: string; + url: string; + version: string; +}; + +export function buildDownloadPlan( + backend: string, + cacheDir: string, + build: Pick, + lockContents: string, +): DownloadPlan { + const version = parsePinnedVersion(lockContents); + const target = laufeyTargetForBuild(build); + const archive = laufeyArchiveName(backend, target); + const sha256 = parsePinnedSha256(lockContents, archive); + if (sha256 == null) { + throw new Error( + `no pinned SHA-256 for ${archive} in cli/laufey_sums.lock`, + ); } - throw new Error( - "could not find a built deno binary; set DENO_BIN or build deno first", + const targetDir = `${cacheDir}/${version}/${backend}/${target}`; + return { + archive, + markerPath: `${targetDir}/.downloaded`, + parentDir: `${cacheDir}/${version}/${backend}`, + sha256: sha256.toLowerCase(), + target, + targetDir, + url: laufeyReleaseUrl(version, archive), + version, + }; +} + +async function sha256Hex(data: Uint8Array): Promise { + const digest = await crypto.subtle.digest( + "SHA-256", + data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength), ); + return Array.from(new Uint8Array(digest), (byte) => + byte.toString(16).padStart(2, "0") + ).join(""); +} + +async function downloadArchive(url: string): Promise { + const response = await fetch(url, { + headers: { + "user-agent": `deno-desktop/${Deno.version.deno} (+https://deno.com)`, + }, + redirect: "follow", + }); + if (!response.ok) { + throw new Error( + `failed to download ${url}: ${response.status} ${response.statusText}`, + ); + } + const data = new Uint8Array(await response.arrayBuffer()); + if (data.length === 0) { + throw new Error(`empty response from ${url}`); + } + return data; } -// laufey v0.6.1 does not ship a `cef` build for aarch64-pc-windows-msvc -// (cli/laufey_sums.lock intentionally has no pin for it), so Windows CI -// legs warm the cache with the `webview` backend instead. -function defaultBackend(): string { - return Deno.build.os === "windows" ? "webview" : "cef"; -} - -const backend = Deno.args[0] ?? defaultBackend(); -const cacheDir = absolute(Deno.args[1] ?? "./target/.native_laufey"); - -const denoBin = resolveDenoBin(); -const warmDir = Deno.makeTempDirSync(); -Deno.writeTextFileSync( - `${warmDir}/main.ts`, - `Deno.serve(() => new Response("ok"));\n`, -); - -console.error(`Downloading laufey "${backend}" backend into ${cacheDir}...`); -// `deno desktop` without `--hmr` only compiles + packages the app - it never -// launches the backend - so this is safe to run headless. -const output = new Deno.Command(denoBin, { - args: [ - "desktop", - `--backend=${backend}`, - "--output", - `${warmDir}/app`, - `${warmDir}/main.ts`, - ], - cwd: warmDir, - env: { DENO_LAUFEY_CACHE_DIR: cacheDir }, - stdout: "inherit", - stderr: "inherit", -}).outputSync(); - -if (!output.success) { - console.error(`failed to download the laufey "${backend}" backend`); - Deno.exit(1); -} - -console.log(cacheDir); +async function runCommand(command: string, args: string[]): Promise { + const output = await new Deno.Command(command, { + args, + stderr: "inherit", + stdout: "inherit", + }).output(); + if (!output.success) { + throw new Error(`command failed: ${command} ${args.join(" ")}`); + } +} + +async function expandZipWithPowerShell( + archivePath: string, + destination: string, +): Promise { + const quotedArchivePath = archivePath.replaceAll("'", "''"); + const quotedDestination = destination.replaceAll("'", "''"); + const command = + `Expand-Archive -LiteralPath '${quotedArchivePath}' ` + + `-DestinationPath '${quotedDestination}' -Force`; + for (const shell of ["pwsh", "powershell"]) { + try { + await runCommand(shell, ["-NoLogo", "-NoProfile", "-Command", command]); + return; + } catch (error) { + if (error instanceof Deno.errors.NotFound) { + continue; + } + } + } + throw new Error("could not find PowerShell to extract laufey zip archive"); +} + +async function extractArchive( + archivePath: string, + archive: string, + destination: string, +): Promise { + if (archive.endsWith(".tar.gz")) { + await runCommand("tar", ["-xzf", archivePath, "-C", destination]); + return; + } + if (archive.endsWith(".zip")) { + try { + await runCommand("tar", ["-xf", archivePath, "-C", destination]); + } catch (error) { + if (Deno.build.os !== "windows") { + throw error; + } + await expandZipWithPowerShell(archivePath, destination); + } + return; + } + throw new Error(`unsupported laufey archive format: ${archive}`); +} + +async function ensureDownloaded(plan: DownloadPlan): Promise { + if (await exists(plan.markerPath)) { + return; + } + + await Deno.mkdir(plan.parentDir, { recursive: true }); + const staging = await Deno.makeTempDir({ + dir: plan.parentDir, + prefix: ".staging-", + }); + + let renamed = false; + try { + const data = await downloadArchive(plan.url); + const actual = await sha256Hex(data); + if (actual !== plan.sha256) { + throw new Error( + `checksum mismatch for ${plan.archive} (downloaded from ${plan.url})\n` + + ` expected: ${plan.sha256}\n` + + ` actual: ${actual}`, + ); + } + + const archivePath = `${staging}/${plan.archive}`; + await Deno.writeFile(archivePath, data); + await extractArchive(archivePath, plan.archive, staging); + await Deno.remove(archivePath); + await Deno.writeTextFile(`${staging}/.downloaded`, `v${plan.version}\n`); + + if (await exists(plan.markerPath)) { + return; + } + + if (await exists(plan.targetDir)) { + await Deno.remove(plan.targetDir, { recursive: true }); + } + + try { + await Deno.rename(staging, plan.targetDir); + renamed = true; + } catch (error) { + if (await exists(plan.markerPath)) { + return; + } + throw error; + } + } finally { + if (!renamed) { + await Deno.remove(staging, { recursive: true }).catch(() => {}); + } + } +} + +async function main() { + const backend = Deno.args[0] ?? defaultBackendFor(Deno.build.os); + const cacheDir = absolute(Deno.args[1] ?? "./target/.native_laufey"); + const lockContents = await Deno.readTextFile(LAUFEY_SUMS_URL); + const plan = buildDownloadPlan(backend, cacheDir, Deno.build, lockContents); + + console.error(`Downloading laufey "${backend}" backend into ${cacheDir}...`); + await ensureDownloaded(plan); + console.log(cacheDir); +} + +if (import.meta.main) { + await main(); +} diff --git a/tools/download_laufey_test.ts b/tools/download_laufey_test.ts new file mode 100644 index 00000000000000..60ba9a29c274a1 --- /dev/null +++ b/tools/download_laufey_test.ts @@ -0,0 +1,121 @@ +// Copyright 2018-2026 the Deno authors. MIT license. + +import { + buildDownloadPlan, + defaultBackendFor, + laufeyArchiveName, + laufeyTargetForBuild, + parsePinnedSha256, + parsePinnedVersion, +} from "./download_laufey.ts"; + +function assertEquals(actual: T, expected: T, message?: string) { + if (actual !== expected) { + throw new Error( + `${message ?? "values are not equal"}\nexpected: ${expected}\nactual: ${actual}`, + ); + } +} + +function assertThrows(fn: () => unknown, expectedText: string) { + try { + fn(); + } catch (error) { + if (error instanceof Error && error.message.includes(expectedText)) { + return; + } + throw error; + } + throw new Error(`expected error containing '${expectedText}'`); +} + +Deno.test("parsePinnedVersion reads lockfile directive", () => { + assertEquals( + parsePinnedVersion("# comment\n# version: v0.6.1\n"), + "0.6.1", + ); +}); + +Deno.test("parsePinnedVersion rejects missing directive", () => { + assertThrows( + () => parsePinnedVersion("95b7dad3 laufey-cef-x86_64-unknown-linux-gnu.tar.gz"), + "missing a '# version: vX.Y.Z' directive", + ); +}); + +Deno.test("parsePinnedSha256 supports GNU sha256sum formats", () => { + const contents = [ + "abc123 laufey-cef-x86_64-unknown-linux-gnu.tar.gz", + "def456 *laufey-webview-x86_64-pc-windows-msvc.zip", + ].join("\n"); + assertEquals( + parsePinnedSha256( + contents, + "laufey-cef-x86_64-unknown-linux-gnu.tar.gz", + ), + "abc123", + ); + assertEquals( + parsePinnedSha256( + contents, + "laufey-webview-x86_64-pc-windows-msvc.zip", + ), + "def456", + ); +}); + +Deno.test("laufeyTargetForBuild maps supported targets", () => { + assertEquals( + laufeyTargetForBuild({ arch: "x86_64", os: "linux" }), + "x86_64-unknown-linux-gnu", + ); + assertEquals( + laufeyTargetForBuild({ arch: "aarch64", os: "darwin" }), + "aarch64-apple-darwin", + ); + assertEquals( + laufeyTargetForBuild({ arch: "x86_64", os: "windows" }), + "x86_64-pc-windows-msvc", + ); +}); + +Deno.test("archive naming keeps raw cache key but uses winit upstream name", () => { + assertEquals(defaultBackendFor("windows"), "webview"); + assertEquals(defaultBackendFor("linux"), "cef"); + assertEquals( + laufeyArchiveName("raw", "x86_64-unknown-linux-gnu"), + "laufey-winit-x86_64-unknown-linux-gnu.tar.gz", + ); + assertEquals( + laufeyArchiveName("webview", "x86_64-pc-windows-msvc"), + "laufey-webview-x86_64-pc-windows-msvc.zip", + ); +}); + +Deno.test("buildDownloadPlan matches resolver cache layout", () => { + const plan = buildDownloadPlan( + "raw", + "/tmp/native_laufey", + { arch: "x86_64", os: "linux" }, + [ + "# version: v0.6.1", + "b0db0c0892181481976da48291ff5befe1cdf81d6e1e2598d6c78b9ed2c616f5 laufey-winit-x86_64-unknown-linux-gnu.tar.gz", + ].join("\n"), + ); + + assertEquals(plan.version, "0.6.1"); + assertEquals(plan.target, "x86_64-unknown-linux-gnu"); + assertEquals(plan.archive, "laufey-winit-x86_64-unknown-linux-gnu.tar.gz"); + assertEquals( + plan.url, + "https://github.com/littledivy/laufey/releases/download/v0.6.1/laufey-winit-x86_64-unknown-linux-gnu.tar.gz", + ); + assertEquals( + plan.targetDir, + "/tmp/native_laufey/0.6.1/raw/x86_64-unknown-linux-gnu", + ); + assertEquals( + plan.markerPath, + "/tmp/native_laufey/0.6.1/raw/x86_64-unknown-linux-gnu/.downloaded", + ); +}); From d33dec27c99e4bb7bb68a4a0654174e0cfef71ee Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 9 Aug 2026 19:02:21 +0000 Subject: [PATCH 24/25] fix(desktop): validate laufey archive downloader Co-authored-by: iownbey <54186227+iownbey@users.noreply.github.com> --- tools/download_laufey.ts | 13 +++++-------- tools/download_laufey_test.ts | 9 +++++++-- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/tools/download_laufey.ts b/tools/download_laufey.ts index 9237983df3d84f..66aab17add15e6 100644 --- a/tools/download_laufey.ts +++ b/tools/download_laufey.ts @@ -134,12 +134,10 @@ export function buildDownloadPlan( } async function sha256Hex(data: Uint8Array): Promise { - const digest = await crypto.subtle.digest( - "SHA-256", - data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength), - ); - return Array.from(new Uint8Array(digest), (byte) => - byte.toString(16).padStart(2, "0") + const digest = await crypto.subtle.digest("SHA-256", Uint8Array.from(data)); + return Array.from( + new Uint8Array(digest), + (byte) => byte.toString(16).padStart(2, "0"), ).join(""); } @@ -179,8 +177,7 @@ async function expandZipWithPowerShell( ): Promise { const quotedArchivePath = archivePath.replaceAll("'", "''"); const quotedDestination = destination.replaceAll("'", "''"); - const command = - `Expand-Archive -LiteralPath '${quotedArchivePath}' ` + + const command = `Expand-Archive -LiteralPath '${quotedArchivePath}' ` + `-DestinationPath '${quotedDestination}' -Force`; for (const shell of ["pwsh", "powershell"]) { try { diff --git a/tools/download_laufey_test.ts b/tools/download_laufey_test.ts index 60ba9a29c274a1..c5e87803b74c8a 100644 --- a/tools/download_laufey_test.ts +++ b/tools/download_laufey_test.ts @@ -12,7 +12,9 @@ import { function assertEquals(actual: T, expected: T, message?: string) { if (actual !== expected) { throw new Error( - `${message ?? "values are not equal"}\nexpected: ${expected}\nactual: ${actual}`, + `${ + message ?? "values are not equal" + }\nexpected: ${expected}\nactual: ${actual}`, ); } } @@ -38,7 +40,10 @@ Deno.test("parsePinnedVersion reads lockfile directive", () => { Deno.test("parsePinnedVersion rejects missing directive", () => { assertThrows( - () => parsePinnedVersion("95b7dad3 laufey-cef-x86_64-unknown-linux-gnu.tar.gz"), + () => + parsePinnedVersion( + "95b7dad3 laufey-cef-x86_64-unknown-linux-gnu.tar.gz", + ), "missing a '# version: vX.Y.Z' directive", ); }); From ee5ad033683641b65f47f7c4251595986ed6fb46 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 9 Aug 2026 19:03:36 +0000 Subject: [PATCH 25/25] test(desktop): harden laufey downloader assertions Co-authored-by: iownbey <54186227+iownbey@users.noreply.github.com> --- tools/download_laufey_test.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tools/download_laufey_test.ts b/tools/download_laufey_test.ts index c5e87803b74c8a..0534173bb6dc61 100644 --- a/tools/download_laufey_test.ts +++ b/tools/download_laufey_test.ts @@ -23,10 +23,13 @@ function assertThrows(fn: () => unknown, expectedText: string) { try { fn(); } catch (error) { - if (error instanceof Error && error.message.includes(expectedText)) { + const message = error instanceof Error ? error.message : String(error); + if (message.includes(expectedText)) { return; } - throw error; + throw new Error( + `expected error containing '${expectedText}', got '${message}'`, + ); } throw new Error(`expected error containing '${expectedText}'`); }