这是indexloc提供的服务,不要输入任何密码
Skip to content
Open
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
117 changes: 107 additions & 10 deletions crates/turborepo-lib/src/query/file.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
use std::sync::Arc;

use async_graphql::{Enum, Object, SimpleObject};
use async_graphql::{Enum, Object, SimpleObject, Union};
use camino::Utf8PathBuf;
use miette::SourceCode;
use swc_ecma_ast::EsVersion;
use swc_ecma_parser::{EsSyntax, Syntax, TsSyntax};
use turbo_trace::Tracer;
use turbopath::AbsoluteSystemPathBuf;
use turborepo_repository::{
change_mapper::{ChangeMapper, GlobalDepsPackageChangeMapper},
package_graph::PackageNode,
};

use crate::{
query::{Array, Diagnostic, Error},
query::{package::Package, Array, Diagnostic, Error, PackageChangeReason},
run::Run,
};

Expand Down Expand Up @@ -166,23 +170,116 @@ impl From<ImportType> for turbo_trace::ImportTraceType {
}
}

#[derive(SimpleObject)]
struct All {
reason: PackageChangeReason,
count: usize,
}

#[derive(Union)]
enum PackageMapping {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Union types in GraphQL are pretty annoying. Maybe I should change this to just return all the packages? I'm a little worried that could be too much data.

All(All),
Package(Package),
}

impl File {
fn get_package(&self) -> Result<Option<PackageMapping>, Error> {
let change_mapper = ChangeMapper::new(
self.run.pkg_dep_graph(),
vec![],
GlobalDepsPackageChangeMapper::new(
self.run.pkg_dep_graph(),
self.run
.root_turbo_json()
.global_deps
.iter()
.map(|dep| dep.as_str()),
)?,
);

// If the file is not in the repo, we can't get the package
let Ok(anchored_path) = self.run.repo_root().anchor(&self.path) else {
return Ok(None);
};

let package = change_mapper
.package_detector()
.detect_package(&anchored_path);

match package {
turborepo_repository::change_mapper::PackageMapping::All(reason) => {
Ok(Some(PackageMapping::All(All {
reason: reason.into(),
count: self.run.pkg_dep_graph().len(),
})))
}
turborepo_repository::change_mapper::PackageMapping::Package((package, _)) => Ok(Some(
PackageMapping::Package(Package::new(self.run.clone(), package.name.clone())?),
)),
turborepo_repository::change_mapper::PackageMapping::None => Ok(None),
}
}
}

#[Object]
impl File {
async fn contents(&self) -> Result<String, Error> {
let contents = self.path.read_to_string()?;
Ok(contents)
Ok(self.path.read_to_string()?)
}

async fn path(&self) -> Result<String, Error> {
Ok(self
.run
// This is `Option` because the file may not be in the repo
async fn path(&self) -> Option<String> {
self.run
.repo_root()
.anchor(&self.path)
.map(|path| path.to_string())?)
.ok()
.map(|path| path.to_string())
}

async fn absolute_path(&self) -> String {
self.path.to_string()
}

async fn package(&self) -> Result<Option<PackageMapping>, Error> {
self.get_package()
}

async fn absolute_path(&self) -> Result<String, Error> {
Ok(self.path.to_string())
/// Gets the affected packages for the file, i.e. all packages that depend
/// on the file.
async fn affected_packages(&self) -> Result<Array<Package>, Error> {
match self.get_package() {
Ok(Some(PackageMapping::All(_))) => {
let mut packages: Vec<_> = self
.run
.pkg_dep_graph()
.packages()
.map(|(name, _)| Package::new(self.run.clone(), name.clone()))
.collect::<Result<Vec<_>, Error>>()?;

packages.sort_by(|a, b| a.get_name().cmp(b.get_name()));
Ok(Array::from(packages))
}
Ok(Some(PackageMapping::Package(package))) => {
let node: PackageNode = PackageNode::Workspace(package.get_name().clone());
let mut ancestors = self
.run
.pkg_dep_graph()
.ancestors(&node)
.iter()
.map(|package| {
Package::new(self.run.clone(), package.as_package_name().clone())
})
// Add the package itself to the list
.chain(std::iter::once(Ok(package.clone())))
.collect::<Result<Vec<_>, Error>>()?;

ancestors.sort_by(|a, b| a.get_name().cmp(b.get_name()));

Ok(Array::from(ancestors))
}
Ok(None) => Ok(Array::default()),
Err(e) => Err(e),
}
}

async fn dependencies(
Expand Down
113 changes: 113 additions & 0 deletions crates/turborepo-lib/src/query/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod server;
mod task;

use std::{
borrow::Cow,
io,
ops::{Deref, DerefMut},
sync::Arc,
Expand Down Expand Up @@ -63,6 +64,10 @@ pub enum Error {
Parse(swc_ecma_parser::error::Error),
#[error(transparent)]
SignalListener(#[from] turborepo_signals::listeners::Error),
#[error(transparent)]
ChangeMapper(#[from] turborepo_repository::change_mapper::Error),
#[error(transparent)]
Scm(#[from] turborepo_scm::Error),
}

pub struct RepositoryQuery {
Expand Down Expand Up @@ -164,6 +169,15 @@ pub struct Array<T: OutputType> {
length: usize,
}

impl<T: OutputType> Default for Array<T> {
fn default() -> Self {
Self {
items: Vec::new(),
length: 0,
}
}
}

impl<T: ObjectType> From<Vec<T>> for Array<T> {
fn from(value: Vec<T>) -> Self {
Self {
Expand Down Expand Up @@ -193,6 +207,22 @@ impl<T: OutputType> FromIterator<T> for Array<T> {
Self { items, length }
}
}

impl<T: OutputType> Array<T> {
pub fn sort_by<F>(&mut self, f: F)
where
F: FnMut(&T, &T) -> std::cmp::Ordering,
{
self.items.sort_by(f);
}
}

impl<T: OutputType> TypeName for Array<T> {
fn type_name() -> Cow<'static, str> {
Cow::Owned(format!("Array<{}>", T::type_name()))
}
}

#[derive(Enum, Copy, Clone, Eq, PartialEq, Debug)]
enum PackageFields {
Name,
Expand Down Expand Up @@ -515,6 +545,44 @@ enum PackageChangeReason {
InFilteredDirectory(InFilteredDirectory),
}

impl From<AllPackageChangeReason> for PackageChangeReason {
fn from(reason: AllPackageChangeReason) -> Self {
match reason {
AllPackageChangeReason::GlobalDepsChanged { file } => {
PackageChangeReason::GlobalDepsChanged(GlobalDepsChanged {
file_path: file.to_string(),
})
}
AllPackageChangeReason::DefaultGlobalFileChanged { file } => {
PackageChangeReason::DefaultGlobalFileChanged(DefaultGlobalFileChanged {
file_path: file.to_string(),
})
}

AllPackageChangeReason::LockfileChangeDetectionFailed => {
PackageChangeReason::LockfileChangeDetectionFailed(LockfileChangeDetectionFailed {
empty: false,
})
}

AllPackageChangeReason::GitRefNotFound { from_ref, to_ref } => {
PackageChangeReason::GitRefNotFound(GitRefNotFound { from_ref, to_ref })
}

AllPackageChangeReason::LockfileChangedWithoutDetails => {
PackageChangeReason::LockfileChangedWithoutDetails(LockfileChangedWithoutDetails {
empty: false,
})
}
AllPackageChangeReason::RootInternalDepChanged { root_internal_dep } => {
PackageChangeReason::RootInternalDepChanged(RootInternalDepChanged {
root_internal_dep: root_internal_dep.to_string(),
})
}
}
}
}

#[derive(SimpleObject)]
struct ChangedPackage {
reason: PackageChangeReason,
Expand Down Expand Up @@ -600,6 +668,51 @@ impl RepositoryQuery {
File::new(self.run.clone(), abs_path)
}

/// Get the files that have changed between the `base` and `head` commits.
///
/// # Arguments
///
/// * `base`: Defaults to `main` or `master`
/// * `head`: Defaults to `HEAD`
/// * `include_uncommitted`: Defaults to `true` if `head` is not provided
/// * `allow_unknown_objects`: Defaults to `false`
/// * `merge_base`: Defaults to `true`
///
/// returns: Result<Array<File>, Error>
async fn affected_files(
&self,
base: Option<String>,
head: Option<String>,
include_uncommitted: Option<bool>,
merge_base: Option<bool>,
) -> Result<Array<File>, Error> {
let base = base.as_deref();
let head = head.as_deref();
let include_uncommitted = include_uncommitted.unwrap_or_else(|| head.is_none());
let merge_base = merge_base.unwrap_or(true);

let repo_root = self.run.repo_root();
let change_result = self
.run
.scm()
.changed_files(
repo_root,
base,
head,
include_uncommitted,
false,
merge_base,
)?
.expect("set allow unknown objects to false");

let files = change_result
.into_iter()
.map(|file| File::new(self.run.clone(), self.run.repo_root().resolve(&file)))
.collect::<Result<Vec<_>, _>>()?;

Ok(Array::from(files))
}

/// Gets a list of packages that match the given filter
async fn packages(&self, filter: Option<PackagePredicate>) -> Result<Array<Package>, Error> {
let Some(filter) = filter else {
Expand Down
25 changes: 11 additions & 14 deletions crates/turborepo-lib/src/query/package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,32 +149,29 @@ impl Package {

async fn all_dependents(&self) -> Result<Array<Package>, Error> {
let node: PackageNode = PackageNode::Workspace(self.name.clone());
Ok(self
let mut dependents: Array<Package> = self
.run
.pkg_dep_graph()
.ancestors(&node)
.iter()
.map(|package| Package {
run: self.run.clone(),
name: package.as_package_name().clone(),
})
.sorted_by(|a, b| a.name.cmp(&b.name))
.collect())
.map(|package| Package::new(self.run.clone(), package.as_package_name().clone()))
.collect::<Result<Array<_>, _>>()?;
dependents.sort_by(|a, b| a.get_name().cmp(b.get_name()));
Ok(dependents)
}

async fn all_dependencies(&self) -> Result<Array<Package>, Error> {
let node: PackageNode = PackageNode::Workspace(self.name.clone());
Ok(self
let mut dependencies: Array<Package> = self
.run
.pkg_dep_graph()
.dependencies(&node)
.iter()
.map(|package| Package {
run: self.run.clone(),
name: package.as_package_name().clone(),
})
.sorted_by(|a, b| a.name.cmp(&b.name))
.collect())
.map(|package| Package::new(self.run.clone(), package.as_package_name().clone()))
.collect::<Result<Array<_>, _>>()?;

dependencies.sort_by(|a, b| a.get_name().cmp(b.get_name()));
Ok(dependencies)
}

/// The downstream packages that depend on this package, indirectly
Expand Down
4 changes: 4 additions & 0 deletions crates/turborepo-paths/src/anchored_system_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ impl AnchoredSystemPath {
buf.unwrap_or_else(|_| panic!("anchored system path is relative: {}", self.0.as_str()))
}

pub fn extension(&self) -> Option<&str> {
self.0.extension()
}

pub fn join_component(&self, segment: &str) -> AnchoredSystemPathBuf {
debug_assert!(!segment.contains(std::path::MAIN_SEPARATOR));
AnchoredSystemPathBuf(self.0.join(segment))
Expand Down
5 changes: 4 additions & 1 deletion crates/turborepo-repository/src/change_mapper/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,6 @@ pub enum PackageChanges {

pub struct ChangeMapper<'a, PD> {
pkg_graph: &'a PackageGraph,

ignore_patterns: Vec<String>,
package_detector: PD,
}
Expand All @@ -132,6 +131,10 @@ impl<'a, PD: PackageChangeMapper> ChangeMapper<'a, PD> {
.find(|f| DEFAULT_GLOBAL_DEPS.iter().any(|dep| *dep == f.as_str()))
}

pub fn package_detector(&self) -> &dyn PackageChangeMapper {
&self.package_detector
}

pub fn changed_packages(
&self,
changed_files: HashSet<AnchoredSystemPathBuf>,
Expand Down
Loading
Loading