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
12 changes: 4 additions & 8 deletions Cargo.lock

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

4 changes: 3 additions & 1 deletion crates/turborepo-cli/src/commands/ls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,8 @@ pub async fn run(
let valid_count = packages
.iter()
.position(|package| {
run.pkg_dep_graph()
run.repo_context()
.pkg_dep_graph()
.package_view(&PackageName::from(package.as_str()))
.is_none()
})
Expand Down Expand Up @@ -226,6 +227,7 @@ async fn query_package_details(
) -> Result<Vec<PackageDetailsDisplay>, cli::Error> {
for package in packages {
if run
.repo_context()
.pkg_dep_graph()
.package_view(&PackageName::from(package.as_str()))
.is_none()
Expand Down
7 changes: 5 additions & 2 deletions crates/turborepo-cli/src/commands/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,8 +284,11 @@ pub async fn run(
{
query
} else {
&fs::read_to_string(AbsoluteSystemPathBuf::from_unknown(run.repo_root(), query))
.map_err(turborepo_query_api::Error::Server)?
&fs::read_to_string(AbsoluteSystemPathBuf::from_unknown(
run.repo_context().repo_root(),
query,
))
.map_err(turborepo_query_api::Error::Server)?
};

let variables_json = variables_path
Expand Down
6 changes: 1 addition & 5 deletions crates/turborepo-query-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,9 @@ license = "MIT"
miette = { workspace = true }
thiserror = { workspace = true }
turbopath = { workspace = true }
turborepo-boundaries = { workspace = true }
turborepo-engine = { workspace = true }
turborepo-repository = { workspace = true }
turborepo-scm = { workspace = true }
turborepo-scope = { workspace = true }
turborepo-run-context = { workspace = true }
turborepo-signals = { workspace = true }
turborepo-turbo-json = { workspace = true }
turborepo-types = { workspace = true }

[lints]
Expand Down
93 changes: 62 additions & 31 deletions crates/turborepo-query-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,6 @@
//!
//! The binary crate implements `QueryServer` and passes it to
//! `turborepo_cli::main()`, connecting the two halves at runtime.
//!
//! Note: this crate's dependency list is larger than ideal for a pure
//! interface crate because `QueryRun` methods expose types from crates
//! like `turborepo-repository` and `turborepo-engine`. The benefit is
//! still realized because the heavy async-graphql/axum/oxc stack in
//! `turborepo-query` doesn't need to compile for `turborepo-run`.

use std::{
collections::{HashMap, HashSet},
Expand All @@ -31,31 +25,67 @@ use std::{

use thiserror::Error;
use turbopath::AnchoredSystemPathBuf;
use turborepo_boundaries::BoundariesResult;
use turborepo_engine::Built;
use turborepo_repository::{change_mapper::PackageInclusionReason, package_graph::PackageName};
use turborepo_run_context::RepoContext;
use turborepo_types::TaskDefinition;

pub type BoundariesFuture<'a> = Pin<
Box<
dyn std::future::Future<Output = Result<BoundariesResult, turborepo_boundaries::Error>>
+ Send
+ 'a,
>,
>;
/// A task identity used by the query contract without exposing the engine's
/// typestate or graph representation.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct QueryTaskId {
pub package: String,
pub task: String,
}

impl QueryTaskId {
pub fn new(package: impl Into<String>, task: impl Into<String>) -> Self {
Self {
package: package.into(),
task: task.into(),
}
}

pub fn full_name(&self) -> String {
self.to_string()
}
}

/// The interface that the query layer requires from a "run" context.
impl std::fmt::Display for QueryTaskId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}#{}", self.package, self.task)
}
}

/// A boundary violation projected into the data needed by the query schema.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct BoundaryDiagnostic {
pub message: String,
pub reason: Option<String>,
pub path: Option<String>,
pub import: Option<String>,
pub start: Option<usize>,
pub end: Option<usize>,
}

pub type BoundariesFuture<'a> =
Pin<Box<dyn Future<Output = Result<Vec<BoundaryDiagnostic>, Error>> + Send + 'a>>;

/// The interface that the query layer requires from a run context.
///
/// Decouples the GraphQL query layer from the concrete `Run` type in
/// turborepo-run, allowing the heavy async-graphql/axum/oxc dependencies
/// to compile in a separate crate.
/// Repository data is exposed through `RepoContext`, while engine data is
/// projected into task IDs, definitions, and graph traversals. This keeps the
/// engine's built-state type and graph implementation out of the interface.
pub trait QueryRun: Send + Sync + 'static {
fn version(&self) -> &'static str;
fn repo_root(&self) -> &turbopath::AbsoluteSystemPath;
fn pkg_dep_graph(&self) -> &turborepo_repository::package_graph::PackageGraph;
fn engine(&self) -> &turborepo_engine::Engine<Built, TaskDefinition>;
fn scm(&self) -> &turborepo_scm::SCM;
fn root_turbo_json(&self) -> &turborepo_turbo_json::TurboJson;
fn repo_context(&self) -> &RepoContext;

fn task_ids(&self) -> Vec<QueryTaskId>;
fn task_ids_for_package(&self, package: &str) -> Vec<QueryTaskId>;
fn task_definition(&self, task_id: &QueryTaskId) -> Option<&TaskDefinition>;
fn task_dependencies(&self, task_id: &QueryTaskId) -> Vec<QueryTaskId>;
fn task_dependents(&self, task_id: &QueryTaskId) -> Vec<QueryTaskId>;
fn transitive_task_dependencies(&self, task_id: &QueryTaskId) -> Vec<QueryTaskId>;
fn transitive_task_dependents(&self, task_id: &QueryTaskId) -> Vec<QueryTaskId>;
fn collect_task_dependencies(&self, task_ids: &HashSet<QueryTaskId>) -> HashSet<QueryTaskId>;

fn calculate_affected_packages(
&self,
Expand All @@ -71,21 +101,25 @@ pub trait QueryRun: Send + Sync + 'static {
head: Option<&str>,
) -> Result<HashSet<AnchoredSystemPathBuf>, AffectedPackagesError>;

/// Matches changed files against task inputs without exposing the engine.
fn match_tasks_against_changed_files(
&self,
changed_files: &HashSet<AnchoredSystemPathBuf>,
) -> Result<HashMap<QueryTaskId, String>, AffectedPackagesError>;

fn check_boundaries(&self, show_progress: bool) -> BoundariesFuture<'_>;
}

#[derive(Debug, Error)]
pub enum AffectedPackagesError {
#[error(transparent)]
Resolution(#[from] turborepo_scope::filter::ResolutionError),
#[error(transparent)]
Other(Box<dyn std::error::Error + Send + Sync>),
}

#[derive(Error, Debug, miette::Diagnostic)]
pub enum Error {
#[error(transparent)]
Boundaries(#[from] turborepo_boundaries::Error),
Boundaries(Box<dyn std::error::Error + Send + Sync>),
#[error("Failed to start GraphQL server.")]
Server(#[from] io::Error),
#[error(transparent)]
Expand All @@ -94,9 +128,6 @@ pub enum Error {
#[error("Failed to calculate affected packages: {0}")]
AffectedPackages(#[from] AffectedPackagesError),
#[error(transparent)]
#[diagnostic(transparent)]
Resolution(#[from] turborepo_scope::filter::ResolutionError),
#[error(transparent)]
SignalListener(#[from] turborepo_signals::listeners::Error),
/// Opaque error from the query implementation crate.
#[error(transparent)]
Expand Down
14 changes: 7 additions & 7 deletions crates/turborepo-query/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,31 +16,31 @@ oxc_ast = { workspace = true }
oxc_estree = { workspace = true }
oxc_parser = { workspace = true }
oxc_span = { workspace = true }
petgraph = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true, features = ["full"] }
tracing = { workspace = true }
turbo-trace = { workspace = true }
turbopath = { workspace = true }
turborepo-boundaries = { workspace = true }
turborepo-engine = { path = "../turborepo-engine" }
turborepo-errors = { workspace = true }
turborepo-lockfiles = { workspace = true }
turborepo-query-api = { workspace = true }
turborepo-repository = { path = "../turborepo-repository" }
turborepo-scm = { workspace = true }
turborepo-scope = { path = "../turborepo-scope" }
turborepo-signals = { workspace = true }
turborepo-task-id = { workspace = true }
turborepo-turbo-json = { path = "../turborepo-turbo-json" }
turborepo-types = { workspace = true }
wax = { workspace = true }
webbrowser = { workspace = true }

[dev-dependencies]
tempfile = { workspace = true }
turborepo-engine = { workspace = true }
turborepo-microfrontends-config = { workspace = true }
turborepo-run-context = { workspace = true }
turborepo-scm = { workspace = true }
turborepo-task-id = { workspace = true }
turborepo-turbo-json = { workspace = true }
turborepo-ui = { workspace = true }

[lints]
workspace = true
Loading
Loading