aboutsummaryrefslogtreecommitdiffstats
path: root/xtask
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2025-06-23 19:19:37 +0200
committerGitHub <noreply@github.com>2025-06-23 19:19:37 +0200
commitc5ac7968809ddb1033b625562eae22019cccc2f4 (patch)
tree932cf07d898dfbcc6ec6a9d4cf5353c06a47accc /xtask
parentfeat(ui): add optional tooltip with key hints to the compact bar (#4225) (diff)
downloadsl0ppij-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.toml1
-rw-r--r--xtask/src/build.rs19
-rw-r--r--xtask/src/ci.rs2
-rw-r--r--xtask/src/clippy.rs1
-rw-r--r--xtask/src/flags.rs16
-rw-r--r--xtask/src/main.rs5
-rw-r--r--xtask/src/metadata.rs89
-rw-r--r--xtask/src/pipelines.rs72
-rw-r--r--xtask/src/test.rs34
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()