diff options
| author | Aram Drevekenin <aram@poor.dev> | 2025-06-23 19:19:37 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-06-23 19:19:37 +0200 |
| commit | c5ac7968809ddb1033b625562eae22019cccc2f4 (patch) | |
| tree | 932cf07d898dfbcc6ec6a9d4cf5353c06a47accc /xtask | |
| parent | feat(ui): add optional tooltip with key hints to the compact bar (#4225) (diff) | |
| download | sl0ppij-c5ac7968809ddb1033b625562eae22019cccc2f4.tar.gz sl0ppij-c5ac7968809ddb1033b625562eae22019cccc2f4.tar.xz sl0ppij-c5ac7968809ddb1033b625562eae22019cccc2f4.zip | |
Feature: web-client/server to share your sessions in the browser (#4242)
* work
* moar work
* notes
* work
* separate to terminal and control channels
* stdin working
* serve html web client initial
* serve static assets loaded with include_dir
* merge
* enable_web_server config parameter
* compile time flag to disable web server capability
* rustfmt
* add license to all xterm.js assets
* mouse working except copy/paste
* helpful comment
* web client improvements
- move script to js file
- add favicon
- add nerd font
- change title
TODO: investigate if font license embedded in otf is sufficient
* get mouse to work properly
* kitty keyboard support initial
* fix wrong type in preload link
* wip axum websocket handlers
- upgrade axum to v0.8.1, enable ws feature
- begin setup of websocket handlers
- tidy up imports
* replace control listener
* handle terminal websocket with axum
* cleanup Cargo.toml
* kitty fixes and bracketed paste
* fix(mouse): pane not found crash
* initial session switching infra
* add `web_client_font` option
* session switching, creation and resurrection working through the session manager
* move session module to zellij-utils and share logic with web-client
* some cleanups
* require restart for enable-web-server
* use session name from router
* write config to disk and watch for config changes
* rename session name to ipc path
* add basic panic handler, make render_to_client exit on channel close
* use while let instead of loop
* handle websocket close
* add mouse motions
* make clipboard work
* add weblink handling and webgl rendering
* add todo
* fix: use session name instead of patch on session switch
* use "default" layout for new sessions
* ui indication for session being shared
* share this session ui
* plugin assets
* Fix process crash on mac with notify watcher.
Use poll watcher instead of recommended as a workaround.
* make url session switching and creation work
* start welcome screen on root url
* scaffold control messages, set font from config
* set dimensions on session start
* bring back session name from url
* send bytes on terminal websocket instead of json
- create web client os input and id before websocket connection
* draft ui
* work
* refactor ui
* remove otf font, remove margins to avoid scrollbar
* version query endpoint for server status
* web session info query endpoint
* refactor: move stuff around
* add web client info to session metadata
* make tests pass
* populate real data in session list
* remove unnecessary endpoint
* add web_client node to config, add font option
* remove web_client_font
* allow disabling the web session through the config - WIP
* formalize sharing/not-sharing configuration
* fix tests
* allow shutting down web server
* display error when web clients are forbidden to attach
* only show sessions that allow web clients if this is a web client
* style(fmt): rustfmt
* fix: query web server from Zellij rather than from each plugin
* remove log spam
* handle some error paths better in the web client
* allow controlling the web server through the cli
* allow configuring the web server's ip/port
* fix tests and format code
* use direct WebServerStatus event instead of piggy-backing on SessionInfo
* plugin revamp initial
* make plugin responsive
* adjust plugin title
* refactor: share plugin
* refactor: share plugin
* add cors middleware
* some fixes for running without a compiled web server capability
* display error when starting the share plugin without web server support
* clarify config
* add pipelines to compile zellij without web support
* display error when unable to start web server
* only query web server when share plugin is running
* refactor(web-client): connection table
* give zellij_server_listener access to the control channel
* fixes and clarifications
* refactor: consolidate generate_unique_session_name
* give proper error when trying to attach to a forbidden session
* change browser URL when switching sessions
* add keyboard shortcut
* enforce https when bound to non-loopback ip
* initial authentication token implementation
* background color from theme
* initial web client theme config
* basic token generation ui
* refactor set config message creation
* also set body background
* allow editing scrollback for plugins too
* set scrollback to 0
* properly parse colors in config
* generate token from plugin
* nice login modals
* initial token management screen
* implement token authentication
* refactor(share): token management screen
* style(fmt): rustfmt
* fix(plugin): some minor bugs
* refactor(share): main screen
* refactor(share): token screen
* refactor(share): main
* refactor(share): ui components
* fix(responsiveness): properly send usage_width to the render function
* fix cli commands and add some verbosity
* add support for settings ansi and selection colors
* add cursor and cursor accent
* basic web client tests
* fix tests
* refactor: web client
* use session tokens for authentication
* improve modals
* move shutdown to ipc
* refactor: ipc logic
* serialize theme config for web client
* update tests
* refactor: move some stuff around to prepare for config hot reload
* config live reloading for the web clients
* change remember-me UI wording
* improve xterm.js link handling
* make sure terminal is focused on mousemove
* remove deprecated sharing indication from compact-bar
* gate deps and functionality behind the web_server_compatibility feature
* feat(build): add --no-web flag in all the places
* fix some other build flows
* add new assets
* update CI for no-web (untested)
* make more dependencies optional
* update axum-extra
* add web client configuration options
* gracefully close connections on server exit
* tests for graceful connection closing
* handle client-side reconnect when server is down
* fix: make sure ipc bus folder exists before starting
* add commands to manage login tokens from the cli
* style(fmt): rustfmt
* some cleanups
* fix(ux): allow alt-right-click on the web client without opening the context menu
* fix: prevent attaching to welcome screen
* fix: reload config issues
* fix long socket path on macos
* normalize config conversion and fix color gap in browser
* revoke session_token cookie if it is not valid
* fix: visual bug with multiple clients in extremely small screen sizes
* fix: only include rusqlite for the web server capability builds
* update e2e snapshots
* refactor(web): client side js
* some cleanups
* moar cleanups
* fix(tests): wait for server instead of using a fixed timeout
* debug CI
* fix(tests): use spawn_blocking for running the test web server
* fix(tests): wait for http rather than tcp port
* fix(tests): properly pass config path - hopefully this is the issue...
* success! bring back the rest of the tests
* attempt to fix the macos CI issue
* docs(changelog): add PR
---------
Co-authored-by: Thomas Linford <linford.t@gmail.com>
Diffstat (limited to 'xtask')
| -rw-r--r-- | xtask/Cargo.toml | 1 | ||||
| -rw-r--r-- | xtask/src/build.rs | 19 | ||||
| -rw-r--r-- | xtask/src/ci.rs | 2 | ||||
| -rw-r--r-- | xtask/src/clippy.rs | 1 | ||||
| -rw-r--r-- | xtask/src/flags.rs | 16 | ||||
| -rw-r--r-- | xtask/src/main.rs | 5 | ||||
| -rw-r--r-- | xtask/src/metadata.rs | 89 | ||||
| -rw-r--r-- | xtask/src/pipelines.rs | 72 | ||||
| -rw-r--r-- | xtask/src/test.rs | 34 |
9 files changed, 216 insertions, 23 deletions
diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index c5405ade..b38ed33e 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] anyhow = { workspace = true } +serde_json = { workspace = true } prost-build = { version = "0.11.9", default-features = false } toml = { version = "0.5", default-features = false } which = { version = "4.2", default-features = false } diff --git a/xtask/src/build.rs b/xtask/src/build.rs index 0cd5bdb0..e150672c 100644 --- a/xtask/src/build.rs +++ b/xtask/src/build.rs @@ -4,7 +4,7 @@ //! //! - [`build`]: Builds general cargo projects (i.e. zellij components) with `cargo build` //! - [`manpage`]: Builds the manpage with `mandown` -use crate::{flags, WorkspaceMember}; +use crate::{flags, metadata, WorkspaceMember}; use anyhow::Context; use std::path::{Path, PathBuf}; use xshell::{cmd, Shell}; @@ -95,6 +95,23 @@ pub fn build(sh: &Shell, flags: flags::Build) -> anyhow::Result<()> { if flags.release { base_cmd = base_cmd.arg("--release"); } + if flags.no_web { + // Check if this crate has web features that need modification + match metadata::get_no_web_features(sh, crate_name) + .context("Failed to check web features")? + { + Some(features) => { + base_cmd = base_cmd.arg("--no-default-features"); + if !features.is_empty() { + base_cmd = base_cmd.arg("--features"); + base_cmd = base_cmd.arg(features); + } + }, + None => { + // Crate doesn't have web features, build normally + }, + } + } base_cmd.run().with_context(err_context)?; if crate_name.contains("plugins") { diff --git a/xtask/src/ci.rs b/xtask/src/ci.rs index b1c5fe2e..b290cd50 100644 --- a/xtask/src/ci.rs +++ b/xtask/src/ci.rs @@ -52,6 +52,7 @@ fn e2e_build(sh: &Shell) -> anyhow::Result<()> { release: true, no_plugins: false, plugins_only: true, + no_web: false, }, ) .context(err_context)?; @@ -154,6 +155,7 @@ fn cross_compile(sh: &Shell, target: &OsString) -> anyhow::Result<()> { release: true, no_plugins: false, plugins_only: true, + no_web: false, }, ) .and_then(|_| build::manpage(sh)) diff --git a/xtask/src/clippy.rs b/xtask/src/clippy.rs index 0cb9146e..77f965b9 100644 --- a/xtask/src/clippy.rs +++ b/xtask/src/clippy.rs @@ -13,6 +13,7 @@ pub fn clippy(sh: &Shell, _flags: flags::Clippy) -> anyhow::Result<()> { release: false, no_plugins: false, plugins_only: true, + no_web: false, }, ) .context("failed to run task 'clippy'")?; diff --git a/xtask/src/flags.rs b/xtask/src/flags.rs index 8ad32870..cf6ad107 100644 --- a/xtask/src/flags.rs +++ b/xtask/src/flags.rs @@ -58,11 +58,15 @@ xflags::xflags! { optional -r, --release /// Clean project before building optional -c, --clean + /// Compile without web server support + optional --no-web } /// Generate a runnable `zellij` executable with plugins bundled cmd install { required destination: PathBuf + /// Compile without web server support + optional --no-web } /// Run debug version of zellij @@ -75,6 +79,8 @@ xflags::xflags! { optional --singlepass /// Disable optimizing dependencies optional --disable-deps-optimize + /// Compile without web server support + optional --no-web /// Arguments to pass after `cargo run --` repeated args: OsString } @@ -87,6 +93,8 @@ xflags::xflags! { /// Run application tests cmd test { + /// Compile without web server support + optional --no-web /// Arguments to pass after `cargo test --` repeated args: OsString } @@ -99,6 +107,8 @@ xflags::xflags! { optional -p, --plugins-only /// Build everything except the plugins optional --no-plugins + /// Compile without web support + optional --no-web } } } @@ -176,11 +186,13 @@ pub struct Clippy; pub struct Make { pub release: bool, pub clean: bool, + pub no_web: bool, } #[derive(Debug)] pub struct Install { pub destination: PathBuf, + pub no_web: bool, } #[derive(Debug)] @@ -191,6 +203,7 @@ pub struct Run { pub data_dir: Option<PathBuf>, pub singlepass: bool, pub disable_deps_optimize: bool, + pub no_web: bool, } #[derive(Debug)] @@ -201,6 +214,7 @@ pub struct Format { #[derive(Debug)] pub struct Test { pub args: Vec<OsString>, + pub no_web: bool, } #[derive(Debug)] @@ -208,6 +222,7 @@ pub struct Build { pub release: bool, pub plugins_only: bool, pub no_plugins: bool, + pub no_web: bool, } impl Xtask { @@ -226,4 +241,3 @@ impl Xtask { Self::from_vec_(args) } } -// generated end diff --git a/xtask/src/main.rs b/xtask/src/main.rs index f1ffaa42..1dac0e2f 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -12,6 +12,7 @@ mod clippy; mod dist; mod flags; mod format; +mod metadata; mod pipelines; mod test; @@ -74,6 +75,10 @@ fn workspace_members() -> &'static Vec<WorkspaceMember> { build: true, }, WorkspaceMember { + crate_name: "default-plugins/share", + build: true, + }, + WorkspaceMember { crate_name: "zellij-utils", build: false, }, diff --git a/xtask/src/metadata.rs b/xtask/src/metadata.rs new file mode 100644 index 00000000..b5efed08 --- /dev/null +++ b/xtask/src/metadata.rs @@ -0,0 +1,89 @@ +//! Helper functions for querying cargo metadata +use anyhow::Context; +use serde_json::Value; +use xshell::{cmd, Shell}; + +/// Get cargo metadata for the workspace +pub fn get_cargo_metadata(sh: &Shell) -> anyhow::Result<Value> { + let cargo = crate::cargo().context("Failed to find cargo executable")?; + let metadata_json = cmd!(sh, "{cargo} metadata --format-version 1 --no-deps") + .read() + .context("Failed to run cargo metadata")?; + + serde_json::from_str(&metadata_json).context("Failed to parse cargo metadata JSON") +} + +/// Get the appropriate features string for a crate when --no-web is enabled +/// Returns Some(features_string) if the crate has web_server_capability and should use --no-default-features +/// Returns None if the crate doesn't have web_server_capability and should use normal build +pub fn get_no_web_features(sh: &Shell, crate_name: &str) -> anyhow::Result<Option<String>> { + let metadata = get_cargo_metadata(sh)?; + + let packages = metadata["packages"] + .as_array() + .context("Expected packages array in metadata")?; + + // First, find the main zellij crate to get the default features + let mut main_default_features = Vec::new(); + for package in packages { + let name = package["name"] + .as_str() + .context("Expected package name as string")?; + + if name == "zellij" { + let features = package["features"] + .as_object() + .context("Expected features object")?; + + if let Some(default_features) = features.get("default").and_then(|v| v.as_array()) { + for feature_value in default_features { + if let Some(feature_name) = feature_value.as_str() { + if feature_name != "web_server_capability" { + main_default_features.push(feature_name); + } + } + } + } + break; + } + } + + // Now check if the target crate has web_server_capability and filter features + for package in packages { + let name = package["name"] + .as_str() + .context("Expected package name as string")?; + + // Handle the root crate case + let matches_crate = if crate_name == "." { + name == "zellij" + } else { + name == crate_name + }; + + if matches_crate { + let features = package["features"] + .as_object() + .context("Expected features object")?; + + // Check if this crate has web_server_capability feature + if !features.contains_key("web_server_capability") { + return Ok(None); + } + + // This crate has web_server_capability, so we need to use --no-default-features + // Only include features that this crate actually has + let mut applicable_features = Vec::new(); + for feature_name in &main_default_features { + if features.contains_key(*feature_name) { + applicable_features.push(*feature_name); + } + } + + // Return the feature string (even if empty) to indicate we should use --no-default-features + return Ok(Some(applicable_features.join(" "))); + } + } + + Ok(None) +} diff --git a/xtask/src/pipelines.rs b/xtask/src/pipelines.rs index d3277c2f..2c7ba2b3 100644 --- a/xtask/src/pipelines.rs +++ b/xtask/src/pipelines.rs @@ -1,7 +1,7 @@ //! Composite pipelines for the build system. //! //! Defines multiple "pipelines" that run specific individual steps in sequence. -use crate::{build, clippy, format, test}; +use crate::{build, clippy, format, metadata, test}; use crate::{flags, WorkspaceMember}; use anyhow::Context; use xshell::{cmd, Shell}; @@ -31,10 +31,19 @@ pub fn make(sh: &Shell, flags: flags::Make) -> anyhow::Result<()> { release: flags.release, no_plugins: false, plugins_only: false, + no_web: flags.no_web, + }, + ) + }) + .and_then(|_| { + test::test( + sh, + flags::Test { + args: vec![], + no_web: flags.no_web, }, ) }) - .and_then(|_| test::test(sh, flags::Test { args: vec![] })) .and_then(|_| clippy::clippy(sh, flags::Clippy {})) .with_context(err_context) } @@ -57,6 +66,7 @@ pub fn install(sh: &Shell, flags: flags::Install) -> anyhow::Result<()> { release: true, no_plugins: false, plugins_only: true, + no_web: flags.no_web, }, ) .and_then(|_| { @@ -67,6 +77,7 @@ pub fn install(sh: &Shell, flags: flags::Install) -> anyhow::Result<()> { release: true, no_plugins: true, plugins_only: false, + no_web: flags.no_web, }, ) }) @@ -111,13 +122,18 @@ pub fn run(sh: &Shell, mut flags: flags::Run) -> anyhow::Result<()> { if let Some(ref data_dir) = flags.data_dir { let data_dir = sh.current_dir().join(data_dir); + let features = if flags.no_web { + "disable_automatic_asset_installation" + } else { + "disable_automatic_asset_installation web_server_capability" + }; crate::cargo() .and_then(|cargo| { cmd!(sh, "{cargo} run") .args(["--package", "zellij"]) .arg("--no-default-features") - .args(["--features", "disable_automatic_asset_installation"]) + .args(["--features", features]) .args(singlepass.iter().flatten()) .args(["--profile", profile]) .args(["--", "--data-dir", &format!("{}", data_dir.display())]) @@ -133,17 +149,51 @@ pub fn run(sh: &Shell, mut flags: flags::Run) -> anyhow::Result<()> { release: false, no_plugins: false, plugins_only: true, + no_web: flags.no_web, }, ) .and_then(|_| crate::cargo()) .and_then(|cargo| { - cmd!(sh, "{cargo} run") - .args(singlepass.iter().flatten()) - .args(["--profile", profile]) - .args(["--"]) - .args(&flags.args) - .run() - .map_err(anyhow::Error::new) + if flags.no_web { + // Use dynamic metadata approach to get the correct features + match metadata::get_no_web_features(sh, ".") + .context("Failed to check web features for main crate")? + { + Some(features) => { + let mut cmd = cmd!(sh, "{cargo} run") + .args(singlepass.iter().flatten()) + .args(["--no-default-features"]); + + if !features.is_empty() { + cmd = cmd.args(["--features", &features]); + } + + cmd.args(["--profile", profile]) + .args(["--"]) + .args(&flags.args) + .run() + .map_err(anyhow::Error::new) + }, + None => { + // Main crate doesn't have web_server_capability, run normally + cmd!(sh, "{cargo} run") + .args(singlepass.iter().flatten()) + .args(["--profile", profile]) + .args(["--"]) + .args(&flags.args) + .run() + .map_err(anyhow::Error::new) + }, + } + } else { + cmd!(sh, "{cargo} run") + .args(singlepass.iter().flatten()) + .args(["--profile", profile]) + .args(["--"]) + .args(&flags.args) + .run() + .map_err(anyhow::Error::new) + } }) .with_context(|| err_context(&flags)) } @@ -167,6 +217,7 @@ pub fn dist(sh: &Shell, _flags: flags::Dist) -> anyhow::Result<()> { sh, flags::Install { destination: crate::project_root().join("./target/dist/zellij"), + no_web: false, }, ) }) @@ -275,6 +326,7 @@ pub fn publish(sh: &Shell, flags: flags::Publish) -> anyhow::Result<()> { release: true, no_plugins: false, plugins_only: true, + no_web: false, }, ) .context(err_context)?; diff --git a/xtask/src/test.rs b/xtask/src/test.rs index 1c725f38..87cda203 100644 --- a/xtask/src/test.rs +++ b/xtask/src/test.rs @@ -1,4 +1,4 @@ -use crate::{build, flags, WorkspaceMember}; +use crate::{build, flags, metadata, WorkspaceMember}; use anyhow::{anyhow, Context}; use std::path::Path; use xshell::{cmd, Shell}; @@ -16,28 +16,42 @@ pub fn test(sh: &Shell, flags: flags::Test) -> anyhow::Result<()> { release: false, no_plugins: false, plugins_only: true, + no_web: flags.no_web, }, ) .context(err_context)?; for WorkspaceMember { crate_name, .. } in crate::workspace_members().iter() { - // the workspace root only contains e2e tests, skip it - if crate_name == &"." { - continue; - } - let _pd = sh.push_dir(Path::new(crate_name)); - // Tell the user where we are now println!(); let msg = format!(">> Testing '{}'", crate_name); crate::status(&msg); println!("{}", msg); - // Override wasm32-wasip1 target for plugins only let cmd = if crate_name.contains("plugins") { cmd!(sh, "{cargo} test --target {host_triple} --") + } else if flags.no_web { + // Check if this crate has web features that need modification + match metadata::get_no_web_features(sh, crate_name) + .context("Failed to check web features")? + { + Some(features) => { + if features.is_empty() { + // Crate has web_server_capability but no other applicable features + cmd!(sh, "{cargo} test --no-default-features --") + } else { + cmd!(sh, "{cargo} test --no-default-features --features") + .arg(features) + .arg("--") + } + }, + None => { + // Crate doesn't have web features, use normal test + cmd!(sh, "{cargo} test --all-features --") + }, + } } else { - cmd!(sh, "{cargo} test --") + cmd!(sh, "{cargo} test --all-features --") }; cmd.args(&flags.args) @@ -47,8 +61,6 @@ pub fn test(sh: &Shell, flags: flags::Test) -> anyhow::Result<()> { Ok(()) } -// Determine the target triple of the host. We explicitly run all tests against the host -// architecture so we can test the plugins, too (they default to wasm32-wasip1 otherwise). pub fn host_target_triple(sh: &Shell) -> anyhow::Result<String> { let rustc_ver = cmd!(sh, "rustc -vV") .read() |
