Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ Options:
- tui: Use the terminal user interface
- stream: Use the standard output stream
- stream-with-experimental-timestamps: Use the standard output stream with timestamps. Note: This feature is experimental and may change or be removed at any time
- web: Use the web user interface. Note: This feature is undocumented, experimental, and not meant to be used. It may change or be removed at any time

--login <LOGIN>
Override the login endpoint
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ Options:
--heap <HEAP>
Specify a file to save a pprof heap profile
--ui <UI>
Specify whether to use the streaming UI or TUI [possible values: tui, stream, stream-with-experimental-timestamps, web]
Specify whether to use the streaming UI or TUI [possible values: tui, stream, stream-with-experimental-timestamps]
--login <LOGIN>
Override the login endpoint
--no-color
Expand Down
1 change: 0 additions & 1 deletion crates/turborepo-lib/src/commands/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,6 @@ pub async fn run(
analytics_handle.close_with_timeout().await;
}

// We only stop if it's the TUI, for the web UI we don't need to stop
if let Some(UISender::Tui(sender)) = sender {
sender.stop().await;
}
Expand Down
5 changes: 2 additions & 3 deletions crates/turborepo-lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,8 @@ pub fn get_version() -> &'static str {
/// Main entry point for the turborepo CLI.
///
/// `query_server` provides the GraphQL query execution layer. When `None`,
/// the `turbo query` command returns an error and the Web UI mode falls
/// back silently. Pass `Some(...)` with a [`QueryServer`] implementation
/// to enable the full query subsystem.
/// the `turbo query` command returns an error. Pass `Some(...)` with a
/// [`QueryServer`] implementation to enable the full query subsystem.
pub fn main(
query_server: Option<std::sync::Arc<dyn turborepo_query_api::QueryServer>>,
) -> Result<i32, shim::Error> {
Expand Down
18 changes: 1 addition & 17 deletions crates/turborepo-lib/src/run/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ pub(crate) mod package_discovery;
pub(crate) mod scope;
pub mod task_access;
pub(crate) mod task_filter;
mod ui;
pub mod watch;

use std::{
Expand Down Expand Up @@ -44,7 +43,7 @@ use turborepo_task_hash::{
};
use turborepo_telemetry::events::generic::GenericEventBuilder;
use turborepo_types::{EnvMode, UIMode};
use turborepo_ui::{sender::UISender, tui, tui::TuiSender, wui::sender::WebUISender, ColorConfig};
use turborepo_ui::{sender::UISender, tui, tui::TuiSender, ColorConfig};

pub use crate::run::error::Error;
use crate::{
Expand Down Expand Up @@ -106,7 +105,6 @@ pub struct Run {

type UIResult<T> = Result<Option<(T, JoinHandle<Result<(), turborepo_ui::Error>>)>, Error>;

type WuiResult = UIResult<WebUISender>;
type TuiResult = UIResult<TuiSender>;

#[derive(Debug, Clone, Copy)]
Expand Down Expand Up @@ -525,22 +523,8 @@ impl Run {
.start_terminal_ui()
.map(|res| res.map(|(sender, handle)| (UISender::Tui(sender), handle))),
UIMode::Stream | UIMode::StreamWithTimestamps => Ok(None),
UIMode::Web => self
.start_web_ui()
.map(|res| res.map(|(sender, handle)| (UISender::Wui(sender), handle))),
}
}
fn start_web_ui(self: &Arc<Self>) -> WuiResult {
let Some(query_server) = self.query_server.clone() else {
tracing::warn!("Web UI requires a query server implementation");
return Ok(None);
};
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();

let handle = tokio::spawn(ui::start_web_ui_server(rx, self.clone(), query_server));

Ok(Some((WebUISender { tx }, handle)))
}

#[allow(clippy::type_complexity)]
fn start_terminal_ui(&self) -> TuiResult {
Expand Down
27 changes: 0 additions & 27 deletions crates/turborepo-lib/src/run/ui.rs

This file was deleted.

1 change: 0 additions & 1 deletion crates/turborepo-query-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ turborepo-scope = { workspace = true }
turborepo-signals = { workspace = true }
turborepo-turbo-json = { workspace = true }
turborepo-types = { workspace = true }
turborepo-ui = { workspace = true }

[lints]
workspace = true
11 changes: 0 additions & 11 deletions crates/turborepo-query-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,6 @@ pub enum Error {
#[error(transparent)]
#[diagnostic(transparent)]
Path(#[from] turbopath::PathError),
#[error(transparent)]
UI(#[from] turborepo_ui::Error),
#[error("Failed to calculate affected packages: {0}")]
AffectedPackages(#[from] AffectedPackagesError),
#[error(transparent)]
Expand Down Expand Up @@ -146,15 +144,6 @@ pub trait QueryServer: Send + Sync {
run: Arc<dyn QueryRun>,
signal: turborepo_signals::SignalHandler,
) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + '_>>;

/// Start the Web UI server that serves the TUI-integrated query interface.
///
/// The shared state is used to stream build events to the UI.
fn run_web_ui_server(
&self,
state: turborepo_ui::wui::query::SharedState,
run: Arc<dyn QueryRun>,
) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + '_>>;
}

// Compile-time assertions that both traits remain object-safe.
Expand Down
1 change: 0 additions & 1 deletion crates/turborepo-query/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ turborepo-signals = { workspace = true }
turborepo-task-id = { workspace = true }
turborepo-turbo-json = { path = "../turborepo-turbo-json" }
turborepo-types = { workspace = true }
turborepo-ui = { workspace = true }
wax = { workspace = true }
webbrowser = { workspace = true }

Expand Down
7 changes: 1 addition & 6 deletions crates/turborepo-query/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,6 @@ impl From<turbopath::PathError> for Error {
Error::Api(e.into())
}
}
impl From<turborepo_ui::Error> for Error {
fn from(e: turborepo_ui::Error) -> Self {
Error::Api(e.into())
}
}
impl From<AffectedPackagesError> for Error {
fn from(e: AffectedPackagesError) -> Self {
Error::Api(e.into())
Expand Down Expand Up @@ -812,7 +807,7 @@ pub async fn run_query_server(run: Arc<dyn QueryRun>, signal: SignalHandler) ->
println!("Shutting down GraphQL server");
return Ok(());
}
result = server::run_server(None, run) => {
result = server::run_server(run) => {
result?;
}
}
Expand Down
24 changes: 4 additions & 20 deletions crates/turborepo-query/src/server.rs
Original file line number Diff line number Diff line change
@@ -1,43 +1,27 @@
use std::sync::Arc;

use async_graphql::{EmptyMutation, EmptySubscription, MergedObject, Schema};
use async_graphql::{EmptyMutation, EmptySubscription, Schema};
use async_graphql_axum::GraphQL;
use axum::{http::Method, routing::get, Router};
use tokio::net::TcpListener;
use tower_http::cors::{Any, CorsLayer};
use turborepo_ui::wui::query::SharedState;

use crate::{graphiql, QueryRun, RepositoryQuery};

#[derive(MergedObject)]
struct Query(turborepo_ui::wui::RunQuery, RepositoryQuery);

pub async fn run_server(
state: Option<SharedState>,
run: Arc<dyn QueryRun>,
) -> Result<(), turborepo_ui::Error> {
pub async fn run_server(run: Arc<dyn QueryRun>) -> std::io::Result<()> {
let cors = CorsLayer::new()
.allow_methods([Method::GET, Method::POST])
.allow_headers(Any)
.allow_origin(Any);

let web_ui_query = turborepo_ui::wui::RunQuery::new(state.clone());
let turbo_query = RepositoryQuery::new(run);
let combined_query = Query(web_ui_query, turbo_query);

let schema = Schema::new(combined_query, EmptyMutation, EmptySubscription);
let schema = Schema::new(turbo_query, EmptyMutation, EmptySubscription);
let app = Router::new()
.route("/", get(graphiql).post_service(GraphQL::new(schema)))
.layer(cors);

axum::serve(
TcpListener::bind("127.0.0.1:8000")
.await
.map_err(turborepo_ui::wui::Error::Server)?,
app,
)
.await
.map_err(turborepo_ui::wui::Error::Server)?;
axum::serve(TcpListener::bind("127.0.0.1:8000").await?, app).await?;

Ok(())
}
11 changes: 2 additions & 9 deletions crates/turborepo-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,12 +185,6 @@ pub enum UIMode {
#[schemars(rename = "stream-with-experimental-timestamps")]
#[value(name = "stream-with-experimental-timestamps")]
StreamWithTimestamps,
/// Use the web user interface.
/// Note: This feature is undocumented, experimental, and not meant to be
/// used. It may change or be removed at any time.
#[schemars(skip)]
#[ts(skip)]
Web,
}

impl fmt::Display for UIMode {
Expand All @@ -199,7 +193,6 @@ impl fmt::Display for UIMode {
UIMode::Tui => write!(f, "tui"),
UIMode::Stream => write!(f, "stream"),
UIMode::StreamWithTimestamps => write!(f, "stream-with-experimental-timestamps"),
UIMode::Web => write!(f, "web"),
}
}
}
Expand All @@ -210,9 +203,9 @@ impl UIMode {
}

/// Returns true if the UI mode has a sender,
/// i.e. web or tui but not stream
/// i.e. tui but not stream
pub fn has_sender(&self) -> bool {
matches!(self, Self::Tui | Self::Web)
matches!(self, Self::Tui)
}

/// Returns true if this UI mode should include timestamps in the prefix
Expand Down
1 change: 0 additions & 1 deletion crates/turborepo-ui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ workspace = true

[dependencies]
async-graphql = { workspace = true }
axum = { workspace = true, features = ["ws"] }
base64 = "0.22"
chrono = { workspace = true }
console = { workspace = true }
Expand Down
3 changes: 1 addition & 2 deletions crates/turborepo-ui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ turborepo-ui
├── PrefixedUI - Output with task prefixes
├── ColorSelector - Assign colors to concurrent tasks
├── LogWriter - Task log handling
├── tui/ - Interactive terminal UI (ratatui)
└── wui/ - Web UI server
└── tui/ - Interactive terminal UI (ratatui)
```

Key components:
Expand Down
3 changes: 0 additions & 3 deletions crates/turborepo-ui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ pub mod sender;
mod terminal_sink;
pub mod tui;
mod tui_sink;
pub mod wui;

use std::{borrow::Cow, env, f64::consts::PI, io::IsTerminal, sync::LazyLock, time::Duration};

Expand Down Expand Up @@ -50,8 +49,6 @@ pub use crate::{
pub enum Error {
#[error(transparent)]
Tui(#[from] tui::Error),
#[error(transparent)]
Wui(#[from] wui::Error),
#[error("Cannot read logs: {0}")]
CannotReadLogs(#[source] std::io::Error),
#[error("Cannot write logs: {0}")]
Expand Down
15 changes: 1 addition & 14 deletions crates/turborepo-ui/src/sender.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,83 +3,70 @@ use std::sync::{Arc, Mutex};
use crate::{
tui,
tui::event::{CacheResult, OutputLogs, PaneSize, TaskResult},
wui::sender,
};

/// Enum to abstract over sending events to either the Tui or the Web UI
/// Enum to abstract over sending events to the TUI.
#[derive(Debug, Clone)]
pub enum UISender {
Tui(tui::TuiSender),
Wui(sender::WebUISender),
}

impl UISender {
pub fn start_task(&self, task: String, output_logs: OutputLogs) {
match self {
UISender::Tui(sender) => sender.start_task(task, output_logs),
UISender::Wui(sender) => sender.start_task(task, output_logs),
}
}

pub fn restart_tasks(&self, tasks: Vec<String>) -> Result<(), crate::Error> {
match self {
UISender::Tui(sender) => sender.restart_tasks(tasks),
UISender::Wui(sender) => sender.restart_tasks(tasks),
}
}

pub fn end_task(&self, task: String, result: TaskResult) {
match self {
UISender::Tui(sender) => sender.end_task(task, result),
UISender::Wui(sender) => sender.end_task(task, result),
}
}

pub fn status(&self, task: String, status: String, result: CacheResult) {
match self {
UISender::Tui(sender) => sender.status(task, status, result),
UISender::Wui(sender) => sender.status(task, status, result),
}
}
fn set_stdin(&self, task: String, stdin: Box<dyn std::io::Write + Send>) {
match self {
UISender::Tui(sender) => sender.set_stdin(task, stdin),
UISender::Wui(sender) => sender.set_stdin(task, stdin),
}
}

pub fn output(&self, task: String, output: Vec<u8>) -> Result<(), crate::Error> {
match self {
UISender::Tui(sender) => sender.output(task, output),
UISender::Wui(sender) => sender.output(task, output),
}
}

/// Construct a sender configured for a specific task
pub fn task(&self, task: String) -> TaskSender {
match self {
UISender::Tui(sender) => sender.task(task),
UISender::Wui(sender) => sender.task(task),
}
}
pub async fn stop(&self) {
match self {
UISender::Tui(sender) => sender.stop().await,
UISender::Wui(sender) => sender.stop(),
}
}
pub fn update_tasks(&self, tasks: Vec<String>) -> Result<(), crate::Error> {
match self {
UISender::Tui(sender) => sender.update_tasks(tasks),
UISender::Wui(sender) => sender.update_tasks(tasks),
}
}

pub async fn pane_size(&self) -> Option<PaneSize> {
match self {
UISender::Tui(sender) => sender.pane_size().await,
// Not applicable to the web UI
UISender::Wui(_) => None,
}
}
}
Expand Down
Loading
Loading