这是indexloc提供的服务,不要输入任何密码
Skip to content
Draft
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
8 changes: 8 additions & 0 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions crates/turborepo-lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ async-graphql = { workspace = true }
async-graphql-axum = { workspace = true }
atty = { workspace = true }
axum = { workspace = true }
base64 = "0.21.0"
biome_deserialize = { workspace = true }
biome_deserialize_macros = { workspace = true }
biome_diagnostics = { workspace = true }
Expand Down Expand Up @@ -147,6 +148,7 @@ turborepo-unescape = { workspace = true }
turborepo-vercel-api = { path = "../turborepo-vercel-api" }
twox-hash = "1.6.3"
uds_windows = "1.0.2"
urlencoding = "2.1.3"
wax = { workspace = true }
webbrowser = { workspace = true }
which = { workspace = true }
Expand Down
7 changes: 6 additions & 1 deletion crates/turborepo-lib/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -747,6 +747,9 @@ pub enum Command {
variables: Option<Utf8PathBuf>,
#[clap(long, conflicts_with = "query")]
schema: bool,
/// Open turborepo.com/graph with the package graph data
#[clap(long)]
graph: bool,
/// The query to run, either a file path or a query string
query: Option<String>,
},
Expand Down Expand Up @@ -1632,18 +1635,20 @@ pub async fn run(
query,
variables,
schema,
graph,
} => {
warn!("query command is experimental and may change in the future");
let query = query.clone();
let variables = variables.clone();
let schema = *schema;
let graph = *graph;
let event = CommandEventBuilder::new("query").with_parent(&root_telemetry);
event.track_call();

let base = CommandBase::new(cli_args, repo_root, version, color_config)?;
event.track_ui_mode(base.opts.run_opts.ui_mode);

let query = query::run(base, event, query, variables.as_deref(), schema).await?;
let query = query::run(base, event, query, variables.as_deref(), schema, graph).await?;

Ok(query)
}
Expand Down
87 changes: 86 additions & 1 deletion crates/turborepo-lib/src/commands/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
commands::CommandBase,
query,
query::{Error, RepositoryQuery},
run::builder::RunBuilder,
run::{builder::RunBuilder, Run},
};

const SCHEMA_QUERY: &str = "query IntrospectionQuery {
Expand Down Expand Up @@ -160,6 +160,7 @@
query: Option<String>,
variables_path: Option<&Utf8Path>,
include_schema: bool,
graph: bool,
) -> Result<i32, Error> {
let signal = get_signal()?;
let handler = SignalHandler::new(signal);
Expand All @@ -168,6 +169,11 @@
.add_all_tasks()
.do_not_validate_engine();
let run = run_builder.build(&handler, telemetry).await?;

if graph {
return handle_graph_mode(run).await;
}

let query = query.as_deref().or(include_schema.then_some(SCHEMA_QUERY));
if let Some(query) = query {
let trimmed_query = query.trim();
Expand Down Expand Up @@ -215,3 +221,82 @@

Ok(0)
}

async fn handle_graph_mode(run: Run) -> Result<i32, Error> {
use std::process::Command;

use base64::{engine::general_purpose::STANDARD, Engine as _};

// Create the GraphQL schema
let schema = Schema::new(
RepositoryQuery::new(Arc::new(run)),
EmptyMutation,
EmptySubscription,
);

// Query to get package graph data
let graph_query = r#"
query {
packageGraph {
nodes {
items {
name
path
}
}
edges {
items {
source
target
}
}
}
}
"#;

let request = Request::new(graph_query);
let result = schema.execute(request).await;

if !result.errors.is_empty() {
for error in result.errors {
eprintln!("GraphQL error: {}", error.message);
}
return Ok(1);
}

// Serialize and base64 encode the graph data
let graph_data = serde_json::to_string(&result.data)?;
let encoded_data = STANDARD.encode(graph_data.as_bytes());

// Determine the base URL based on build profile
let base_url = if cfg!(debug_assertions) {
"http://localhost:3000"
} else {
"https://turborepo.com"
};

let graph_url = format!("{}/graph?data={}", base_url, encoded_data);

// Open the URL in the default browser
let status = if cfg!(target_os = "windows") {
Command::new("cmd")
.args(&["/C", "start", &graph_url])

Check failure on line 283 in crates/turborepo-lib/src/commands/query.rs

View workflow job for this annotation

GitHub Actions / Rust lints

the borrowed expression implements the required traits
.status()
} else if cfg!(target_os = "macos") {
Command::new("open").arg(&graph_url).status()
} else {
Command::new("xdg-open").arg(&graph_url).status()
};

match status {
Ok(_) => {
println!("Opening {} with your package graph data...", base_url);
Ok(0)
}
Err(e) => {
eprintln!("Failed to open browser: {}", e);
println!("Please manually open: {}", base_url);
Ok(0)
}
}
}
31 changes: 31 additions & 0 deletions docs/site/app/(no-sidebar)/graph/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"use client";

import { useSearchParams } from "next/navigation";
import { useEffect, useState } from "react";
import { GraphVisualization } from "#components/graph-visualization.tsx";

export const Page = () => {
const searchParams = useSearchParams();
const [decodedPayload, setDecodedPayload] = useState<string | null>(null);

useEffect(() => {
const searchParam = searchParams.get("data");

if (searchParam) {
try {
const decoded = atob(searchParam);
setDecodedPayload(decoded);
} catch (error) {
// Silently handle decoding errors
}
}
}, [searchParams]);

return (
<>
<GraphVisualization initialData={decodedPayload} />
</>
);
};

export default Page;
Loading
Loading