Skip to content

Commit 96183a6

Browse files
committed
Implemented web UI
1 parent 59312c1 commit 96183a6

39 files changed

+9786
-5759
lines changed

Cargo.lock

+68
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/turborepo-auth/src/login_server.rs

-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ pub trait LoginServer {
3939
token: Arc<OnceCell<String>>,
4040
) -> Result<(), Error>;
4141
}
42-
4342
/// A struct that implements LoginServer.
4443
///
4544
/// Listens on 127.0.0.1 and a port that's passed in.

crates/turborepo-lib/src/commands/run.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,15 @@ pub async fn run(base: CommandBase, telemetry: CommandEventBuilder) -> Result<i3
4444
.build(&handler, telemetry)
4545
.await?;
4646

47-
let (sender, handle) = run.start_experimental_ui()?.unzip();
47+
let (sender, handle) = run.start_ui()?.unzip();
48+
4849
let result = run.run(sender.clone(), false).await;
4950

5051
if let Some(analytics_handle) = analytics_handle {
5152
analytics_handle.close_with_timeout().await;
5253
}
5354

54-
if let (Some(handle), Some(sender)) = (handle, sender) {
55-
sender.stop();
55+
if let Some(handle) = handle {
5656
if let Err(e) = handle.await.expect("render thread panicked") {
5757
error!("error encountered rendering tui: {e}");
5858
}

crates/turborepo-lib/src/daemon/default_timeout_layer.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ mod test {
8585
sync::{Arc, Mutex},
8686
};
8787

88-
use axum::http::HeaderValue;
88+
use reqwest::header::HeaderValue;
8989
use test_case::test_case;
9090

9191
use super::*;

crates/turborepo-lib/src/opts.rs

+5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use crate::{
1212
commands::CommandBase,
1313
config::ConfigurationOptions,
1414
run::task_id::TaskId,
15+
turbo_json::UIMode,
1516
};
1617

1718
#[derive(Debug, Error)]
@@ -160,6 +161,7 @@ pub struct RunOpts {
160161
pub summarize: Option<Option<bool>>,
161162
pub(crate) experimental_space_id: Option<String>,
162163
pub is_github_actions: bool,
164+
pub ui_mode: UIMode,
163165
}
164166

165167
impl RunOpts {
@@ -260,6 +262,7 @@ impl<'a> TryFrom<OptsInputs<'a>> for RunOpts {
260262
dry_run: inputs.run_args.dry_run,
261263
env_mode: inputs.config.env_mode(),
262264
is_github_actions,
265+
ui_mode: inputs.config.ui(),
263266
})
264267
}
265268
}
@@ -379,6 +382,7 @@ mod test {
379382
use crate::{
380383
cli::DryRunMode,
381384
opts::{Opts, RunCacheOpts, ScopeOpts},
385+
turbo_json::UIMode,
382386
};
383387

384388
#[derive(Default)]
@@ -465,6 +469,7 @@ mod test {
465469
only: opts_input.only,
466470
dry_run: opts_input.dry_run,
467471
graph: None,
472+
ui_mode: UIMode::Tui,
468473
single_package: false,
469474
log_prefix: crate::opts::ResolvedLogPrefix::Task,
470475
log_order: crate::opts::ResolvedLogOrder::Stream,

crates/turborepo-lib/src/run/builder.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ pub struct RunBuilder {
5656
repo_root: AbsoluteSystemPathBuf,
5757
ui: UI,
5858
version: &'static str,
59-
ui_mode: UIMode,
6059
api_client: APIClient,
6160
analytics_sender: Option<AnalyticsSender>,
6261
// In watch mode, we can have a changed package that we want to serve as an entrypoint.
@@ -77,13 +76,12 @@ impl RunBuilder {
7776
let allow_missing_package_manager = config.allow_no_package_manager();
7877

7978
let version = base.version();
80-
let ui_mode = config.ui();
8179
let processes = ProcessManager::new(
8280
// We currently only use a pty if the following are met:
8381
// - we're attached to a tty
8482
atty::is(atty::Stream::Stdout) &&
8583
// - if we're on windows, we're using the UI
86-
(!cfg!(windows) || matches!(ui_mode, UIMode::Tui)),
84+
(!cfg!(windows) || matches!(opts.run_opts.ui_mode, UIMode::Tui)),
8785
);
8886
let CommandBase { repo_root, ui, .. } = base;
8987

@@ -94,7 +92,6 @@ impl RunBuilder {
9492
repo_root,
9593
ui,
9694
version,
97-
ui_mode,
9895
api_auth,
9996
analytics_sender: None,
10097
entrypoint_packages: None,
@@ -384,7 +381,6 @@ impl RunBuilder {
384381
Ok(Run {
385382
version: self.version,
386383
ui: self.ui,
387-
ui_mode: self.ui_mode,
388384
start_at,
389385
processes: self.processes,
390386
run_telemetry,
@@ -439,7 +435,11 @@ impl RunBuilder {
439435

440436
if !self.opts.run_opts.parallel {
441437
engine
442-
.validate(pkg_dep_graph, self.opts.run_opts.concurrency, self.ui_mode)
438+
.validate(
439+
pkg_dep_graph,
440+
self.opts.run_opts.concurrency,
441+
self.opts.run_opts.ui_mode,
442+
)
443443
.map_err(Error::EngineValidation)?;
444444
}
445445

crates/turborepo-lib/src/run/error.rs

+2
Original file line numberDiff line numberDiff line change
@@ -57,5 +57,7 @@ pub enum Error {
5757
#[error(transparent)]
5858
Daemon(#[from] daemon::DaemonError),
5959
#[error(transparent)]
60+
UI(#[from] turborepo_ui::Error),
61+
#[error(transparent)]
6062
Tui(#[from] tui::Error),
6163
}

crates/turborepo-lib/src/run/mod.rs

+33-15
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ use turborepo_env::EnvironmentVariableMap;
3131
use turborepo_repository::package_graph::{PackageGraph, PackageName, PackageNode};
3232
use turborepo_scm::SCM;
3333
use turborepo_telemetry::events::generic::GenericEventBuilder;
34-
use turborepo_ui::{cprint, cprintln, tui, tui::AppSender, BOLD_GREY, GREY, UI};
34+
use turborepo_ui::{
35+
cprint, cprintln, sender::UISender, tui, tui::TuiSender, wui::WebUISender, BOLD_GREY, GREY, UI,
36+
};
3537

3638
pub use crate::run::error::Error;
3739
use crate::{
@@ -69,9 +71,12 @@ pub struct Run {
6971
task_access: TaskAccess,
7072
daemon: Option<DaemonClient<DaemonConnector>>,
7173
should_print_prelude: bool,
72-
ui_mode: UIMode,
7374
}
7475

76+
type UIResult = Result<Option<(UISender, JoinHandle<Result<(), turborepo_ui::Error>>)>, Error>;
77+
type WuiResult = Result<Option<(WebUISender, JoinHandle<Result<(), turborepo_ui::Error>>)>, Error>;
78+
type TuiResult = Result<Option<(TuiSender, JoinHandle<Result<(), turborepo_ui::Error>>)>, Error>;
79+
7580
impl Run {
7681
fn has_persistent_tasks(&self) -> bool {
7782
self.engine.has_persistent_tasks
@@ -173,19 +178,36 @@ impl Run {
173178
}
174179

175180
pub fn has_tui(&self) -> bool {
176-
self.ui_mode.use_tui()
181+
self.opts.run_opts.ui_mode.use_tui()
177182
}
178183

179184
pub fn should_start_ui(&self) -> Result<bool, Error> {
180-
Ok(self.ui_mode.use_tui()
185+
Ok(self.opts.run_opts.ui_mode.use_tui()
181186
&& self.opts.run_opts.dry_run.is_none()
182187
&& tui::terminal_big_enough()?)
183188
}
184189

190+
pub fn start_ui(&self) -> UIResult {
191+
match self.opts.run_opts.ui_mode {
192+
UIMode::Tui => self
193+
.start_terminal_ui()
194+
.map(|res| res.map(|(sender, handle)| (UISender::Tui(sender), handle))),
195+
UIMode::Stream => Ok(None),
196+
UIMode::Web => self
197+
.start_web_ui()
198+
.map(|res| res.map(|(sender, handle)| (UISender::Wui(sender), handle))),
199+
}
200+
}
201+
pub fn start_web_ui(&self) -> WuiResult {
202+
let (tx, rx) = tokio::sync::broadcast::channel(100);
203+
204+
let handle = tokio::spawn(turborepo_ui::wui::start_ws_server(rx));
205+
206+
Ok(Some((WebUISender { tx }, handle)))
207+
}
208+
185209
#[allow(clippy::type_complexity)]
186-
pub fn start_experimental_ui(
187-
&self,
188-
) -> Result<Option<(AppSender, JoinHandle<Result<(), tui::Error>>)>, Error> {
210+
pub fn start_terminal_ui(&self) -> TuiResult {
189211
// Print prelude here as this needs to happen before the UI is started
190212
if self.should_print_prelude {
191213
self.print_run_prelude();
@@ -201,8 +223,8 @@ impl Run {
201223
return Ok(None);
202224
}
203225

204-
let (sender, receiver) = AppSender::new();
205-
let handle = tokio::task::spawn_blocking(move || tui::run_app(task_names, receiver));
226+
let (sender, receiver) = TuiSender::new();
227+
let handle = tokio::task::spawn_blocking(move || Ok(tui::run_app(task_names, receiver)?));
206228

207229
Ok(Some((sender, handle)))
208230
}
@@ -214,11 +236,7 @@ impl Run {
214236
}
215237
}
216238

217-
pub async fn run(
218-
&mut self,
219-
experimental_ui_sender: Option<AppSender>,
220-
is_watch: bool,
221-
) -> Result<i32, Error> {
239+
pub async fn run(&mut self, ui_sender: Option<UISender>, is_watch: bool) -> Result<i32, Error> {
222240
let skip_cache_writes = self.opts.runcache_opts.skip_writes;
223241
if let Some(subscriber) = self.signal_handler.subscribe() {
224242
let run_cache = self.run_cache.clone();
@@ -405,7 +423,7 @@ impl Run {
405423
self.processes.clone(),
406424
&self.repo_root,
407425
global_env,
408-
experimental_ui_sender,
426+
ui_sender,
409427
is_watch,
410428
);
411429

0 commit comments

Comments
 (0)