diff --git a/Cargo.lock b/Cargo.lock index 4f6a252fd4918..a811166db0009 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6805,6 +6805,7 @@ dependencies = [ "turborepo-repository", "turborepo-scm", "turborepo-signals", + "turborepo-task-id", "turborepo-telemetry", "turborepo-ui", "turborepo-unescape", @@ -6986,6 +6987,16 @@ dependencies = [ "tokio", ] +[[package]] +name = "turborepo-task-id" +version = "0.1.0" +dependencies = [ + "serde", + "test-case", + "thiserror 1.0.63", + "turborepo-repository", +] + [[package]] name = "turborepo-telemetry" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index dee9f5287f73c..a5a4333cf9d7c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,6 +65,7 @@ turborepo-lockfiles = { path = "crates/turborepo-lockfiles" } turborepo-microfrontends = { path = "crates/turborepo-microfrontends" } turborepo-process = { path = "crates/turborepo-process" } turborepo-repository = { path = "crates/turborepo-repository" } +turborepo-task-id = { path = "crates/turborepo-task-id" } turborepo-ui = { path = "crates/turborepo-ui" } turborepo-unescape = { path = "crates/turborepo-unescape" } turborepo-scm = { path = "crates/turborepo-scm" } diff --git a/crates/turborepo-lib/Cargo.toml b/crates/turborepo-lib/Cargo.toml index 2ae8b55048983..d8692d69b1c0f 100644 --- a/crates/turborepo-lib/Cargo.toml +++ b/crates/turborepo-lib/Cargo.toml @@ -141,6 +141,7 @@ turborepo-process = { workspace = true } turborepo-repository = { path = "../turborepo-repository" } turborepo-scm = { workspace = true, features = ["git2"] } turborepo-signals = { workspace = true } +turborepo-task-id = { workspace = true } turborepo-telemetry = { path = "../turborepo-telemetry" } turborepo-ui = { workspace = true } turborepo-unescape = { workspace = true } diff --git a/crates/turborepo-lib/src/commands/mod.rs b/crates/turborepo-lib/src/commands/mod.rs index 5368b3395c2d7..bc65de0e5c872 100644 --- a/crates/turborepo-lib/src/commands/mod.rs +++ b/crates/turborepo-lib/src/commands/mod.rs @@ -8,9 +8,11 @@ use turborepo_ui::ColorConfig; use crate::{ cli, - config::{ConfigurationOptions, Error as ConfigError, TurborepoConfigBuilder}, + config::{ + resolve_turbo_config_path, ConfigurationOptions, Error as ConfigError, + TurborepoConfigBuilder, + }, opts::Opts, - turbo_json::resolve_turbo_config_path, Args, }; diff --git a/crates/turborepo-lib/src/commands/prune.rs b/crates/turborepo-lib/src/commands/prune.rs index ae062a5ce5845..f57add8b76844 100644 --- a/crates/turborepo-lib/src/commands/prune.rs +++ b/crates/turborepo-lib/src/commands/prune.rs @@ -16,7 +16,10 @@ use turborepo_telemetry::events::command::CommandEventBuilder; use turborepo_ui::BOLD; use super::CommandBase; -use crate::turbo_json::{RawTurboJson, CONFIG_FILE, CONFIG_FILE_JSONC}; +use crate::{ + config::{CONFIG_FILE, CONFIG_FILE_JSONC}, + turbo_json::RawTurboJson, +}; pub const DEFAULT_OUTPUT_DIR: &str = "out"; diff --git a/crates/turborepo-lib/src/config/mod.rs b/crates/turborepo-lib/src/config/mod.rs index a05448adc7c74..f7e9f497d03f2 100644 --- a/crates/turborepo-lib/src/config/mod.rs +++ b/crates/turborepo-lib/src/config/mod.rs @@ -23,11 +23,11 @@ use turborepo_cache::CacheConfig; use turborepo_errors::TURBO_SITE; use turborepo_repository::package_graph::PackageName; +use crate::cli::{EnvMode, LogOrder}; pub use crate::turbo_json::{RawTurboJson, UIMode}; -use crate::{ - cli::{EnvMode, LogOrder}, - turbo_json::resolve_turbo_config_path, -}; + +pub const CONFIG_FILE: &str = "turbo.json"; +pub const CONFIG_FILE_JSONC: &str = "turbo.jsonc"; #[derive(Debug, Error, Diagnostic)] #[error("Environment variables should not be prefixed with \"{env_pipeline_delimiter}\"")] @@ -566,6 +566,32 @@ impl TurborepoConfigBuilder { } } +/// Given a directory path, determines which turbo.json configuration file to +/// use. Returns an error if both turbo.json and turbo.jsonc exist in the same +/// directory. Returns the path to the config file to use, defaulting to +/// turbo.json if neither exists. +pub fn resolve_turbo_config_path( + dir_path: &turbopath::AbsoluteSystemPath, +) -> Result { + use crate::config::Error; + + let turbo_json_path = dir_path.join_component(CONFIG_FILE); + let turbo_jsonc_path = dir_path.join_component(CONFIG_FILE_JSONC); + + let turbo_json_exists = turbo_json_path.try_exists()?; + let turbo_jsonc_exists = turbo_jsonc_path.try_exists()?; + + match (turbo_json_exists, turbo_jsonc_exists) { + (true, true) => Err(Error::MultipleTurboConfigs { + directory: dir_path.to_string(), + }), + (true, false) => Ok(turbo_json_path), + (false, true) => Ok(turbo_jsonc_path), + // Default to turbo.json if neither exists + (false, false) => Ok(turbo_json_path), + } +} + #[cfg(test)] mod test { use std::{collections::HashMap, ffi::OsString}; @@ -573,12 +599,9 @@ mod test { use tempfile::TempDir; use turbopath::{AbsoluteSystemPath, AbsoluteSystemPathBuf}; - use crate::{ - config::{ - ConfigurationOptions, TurborepoConfigBuilder, DEFAULT_API_URL, DEFAULT_LOGIN_URL, - DEFAULT_TIMEOUT, - }, - turbo_json::{CONFIG_FILE, CONFIG_FILE_JSONC}, + use crate::config::{ + ConfigurationOptions, TurborepoConfigBuilder, CONFIG_FILE, CONFIG_FILE_JSONC, + DEFAULT_API_URL, DEFAULT_LOGIN_URL, DEFAULT_TIMEOUT, }; #[test] diff --git a/crates/turborepo-lib/src/config/turbo_json.rs b/crates/turborepo-lib/src/config/turbo_json.rs index 7f4c87a0cbe3d..269474a75fbcb 100644 --- a/crates/turborepo-lib/src/config/turbo_json.rs +++ b/crates/turborepo-lib/src/config/turbo_json.rs @@ -2,12 +2,44 @@ use camino::Utf8PathBuf; use turbopath::{AbsoluteSystemPath, RelativeUnixPath}; use super::{ConfigurationOptions, Error, ResolvedConfigurationOptions}; -use crate::turbo_json::RawTurboJson; +use crate::turbo_json::{RawRemoteCacheOptions, RawTurboJson}; pub struct TurboJsonReader<'a> { repo_root: &'a AbsoluteSystemPath, } +impl From<&RawRemoteCacheOptions> for ConfigurationOptions { + fn from(remote_cache_opts: &RawRemoteCacheOptions) -> Self { + Self { + api_url: remote_cache_opts + .api_url + .as_ref() + .map(|s| s.as_inner().clone()), + login_url: remote_cache_opts + .login_url + .as_ref() + .map(|s| s.as_inner().clone()), + team_slug: remote_cache_opts + .team_slug + .as_ref() + .map(|s| s.as_inner().clone()), + team_id: remote_cache_opts + .team_id + .as_ref() + .map(|s| s.as_inner().clone()), + signature: remote_cache_opts.signature.as_ref().map(|s| *s.as_inner()), + preflight: remote_cache_opts.preflight.as_ref().map(|s| *s.as_inner()), + timeout: remote_cache_opts.timeout.as_ref().map(|s| *s.as_inner()), + upload_timeout: remote_cache_opts + .upload_timeout + .as_ref() + .map(|s| *s.as_inner()), + enabled: remote_cache_opts.enabled.as_ref().map(|s| *s.as_inner()), + ..Self::default() + } + } +} + impl<'a> TurboJsonReader<'a> { pub fn new(repo_root: &'a AbsoluteSystemPath) -> Self { Self { repo_root } @@ -68,7 +100,7 @@ mod test { use tempfile::tempdir; use super::*; - use crate::turbo_json::CONFIG_FILE; + use crate::config::CONFIG_FILE; #[test] fn test_reads_from_default() { diff --git a/crates/turborepo-lib/src/engine/builder.rs b/crates/turborepo-lib/src/engine/builder.rs index 32253ad51a4e7..1cf95f1cbcdc5 100644 --- a/crates/turborepo-lib/src/engine/builder.rs +++ b/crates/turborepo-lib/src/engine/builder.rs @@ -7,11 +7,11 @@ use turbopath::{AbsoluteSystemPath, AnchoredSystemPathBuf, RelativeUnixPathBuf}; use turborepo_errors::{Spanned, TURBO_SITE}; use turborepo_graph_utils as graph; use turborepo_repository::package_graph::{PackageGraph, PackageName, PackageNode, ROOT_PKG_NAME}; +use turborepo_task_id::{TaskId, TaskName}; use super::Engine; use crate::{ config, - run::task_id::{TaskId, TaskName}, task_graph::TaskDefinition, turbo_json::{ validate_extends, validate_no_package_task_syntax, validate_with_has_no_topo, diff --git a/crates/turborepo-lib/src/engine/execute.rs b/crates/turborepo-lib/src/engine/execute.rs index 79a84b887daf4..62ddffefc75eb 100644 --- a/crates/turborepo-lib/src/engine/execute.rs +++ b/crates/turborepo-lib/src/engine/execute.rs @@ -4,9 +4,9 @@ use futures::{stream::FuturesUnordered, StreamExt}; use tokio::sync::{mpsc, oneshot, Semaphore}; use tracing::log::debug; use turborepo_graph_utils::Walker; +use turborepo_task_id::TaskId; use super::{Engine, TaskNode}; -use crate::run::task_id::TaskId; pub struct Message { pub info: T, diff --git a/crates/turborepo-lib/src/engine/mod.rs b/crates/turborepo-lib/src/engine/mod.rs index 2f06c7328862e..d3028a3eee041 100644 --- a/crates/turborepo-lib/src/engine/mod.rs +++ b/crates/turborepo-lib/src/engine/mod.rs @@ -16,8 +16,9 @@ use petgraph::Graph; use thiserror::Error; use turborepo_errors::Spanned; use turborepo_repository::package_graph::{PackageGraph, PackageName}; +use turborepo_task_id::TaskId; -use crate::{run::task_id::TaskId, task_graph::TaskDefinition, turbo_json::UIMode}; +use crate::{task_graph::TaskDefinition, turbo_json::UIMode}; #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum TaskNode { @@ -612,9 +613,9 @@ mod test { discovery::{DiscoveryResponse, PackageDiscovery, WorkspaceData}, package_json::PackageJson, }; + use turborepo_task_id::TaskName; use super::*; - use crate::run::task_id::TaskName; struct DummyDiscovery<'a>(&'a TempDir); diff --git a/crates/turborepo-lib/src/microfrontends.rs b/crates/turborepo-lib/src/microfrontends.rs index fe950c9a3ac07..20c6f6dae2833 100644 --- a/crates/turborepo-lib/src/microfrontends.rs +++ b/crates/turborepo-lib/src/microfrontends.rs @@ -5,12 +5,9 @@ use tracing::warn; use turbopath::{AbsoluteSystemPath, RelativeUnixPath, RelativeUnixPathBuf}; use turborepo_microfrontends::{Config as MFEConfig, Error, MICROFRONTENDS_PACKAGE}; use turborepo_repository::package_graph::{PackageGraph, PackageName}; +use turborepo_task_id::{TaskId, TaskName}; -use crate::{ - config, - run::task_id::{TaskId, TaskName}, - turbo_json::TurboJson, -}; +use crate::{config, turbo_json::TurboJson}; #[derive(Debug, Clone)] pub struct MicrofrontendsConfigs { @@ -309,7 +306,7 @@ mod test { $( let mut _dev_tasks = std::collections::HashMap::new(); for _dev_task in $dev_tasks.as_slice() { - let _dev_task_id = crate::run::task_id::TaskName::from(*_dev_task).task_id().unwrap().into_owned(); + let _dev_task_id = turborepo_task_id::TaskName::from(*_dev_task).task_id().unwrap().into_owned(); let _dev_application = _dev_task_id.package().to_owned(); _dev_tasks.insert(_dev_task_id, _dev_application); } @@ -382,7 +379,7 @@ mod test { } fn str_to_task(s: &str) -> TaskId<'static> { - crate::run::task_id::TaskName::from(s) + turborepo_task_id::TaskName::from(s) .task_id() .unwrap() .into_owned() diff --git a/crates/turborepo-lib/src/opts.rs b/crates/turborepo-lib/src/opts.rs index 01a80ac250744..bec6db82cda68 100644 --- a/crates/turborepo-lib/src/opts.rs +++ b/crates/turborepo-lib/src/opts.rs @@ -6,15 +6,15 @@ use thiserror::Error; use turbopath::{AbsoluteSystemPath, AbsoluteSystemPathBuf, AnchoredSystemPathBuf}; use turborepo_api_client::APIAuth; use turborepo_cache::{CacheOpts, RemoteCacheOpts}; +use turborepo_task_id::{TaskId, TaskName}; use crate::{ cli::{ Command, ContinueMode, DryRunMode, EnvMode, ExecutionArgs, LogOrder, LogPrefix, OutputLogsMode, RunArgs, }, - config::ConfigurationOptions, - run::task_id::{TaskId, TaskName}, - turbo_json::{UIMode, CONFIG_FILE}, + config::{ConfigurationOptions, CONFIG_FILE}, + turbo_json::UIMode, Args, }; @@ -567,16 +567,16 @@ mod test { use test_case::test_case; use turbopath::AbsoluteSystemPathBuf; use turborepo_cache::{CacheActions, CacheConfig, CacheOpts}; + use turborepo_task_id::TaskId; use turborepo_ui::ColorConfig; use super::{APIClientOpts, RepoOpts, RunOpts, TaskArgs}; use crate::{ cli::{Command, ContinueMode, DryRunMode, RunArgs}, commands::CommandBase, - config::ConfigurationOptions, + config::{ConfigurationOptions, CONFIG_FILE}, opts::{Opts, RunCacheOpts, ScopeOpts, TuiOpts}, - run::task_id::TaskId, - turbo_json::{UIMode, CONFIG_FILE}, + turbo_json::UIMode, Args, }; diff --git a/crates/turborepo-lib/src/package_changes_watcher.rs b/crates/turborepo-lib/src/package_changes_watcher.rs index a902f5f36dabe..3ddea38b062fe 100644 --- a/crates/turborepo-lib/src/package_changes_watcher.rs +++ b/crates/turborepo-lib/src/package_changes_watcher.rs @@ -23,8 +23,9 @@ use turborepo_repository::{ }; use turborepo_scm::GitHashes; -use crate::turbo_json::{ - resolve_turbo_config_path, TurboJson, TurboJsonLoader, CONFIG_FILE, CONFIG_FILE_JSONC, +use crate::{ + config::{resolve_turbo_config_path, CONFIG_FILE, CONFIG_FILE_JSONC}, + turbo_json::{TurboJson, TurboJsonLoader}, }; #[derive(Clone)] diff --git a/crates/turborepo-lib/src/query/task.rs b/crates/turborepo-lib/src/query/task.rs index 72997203071ec..3c30c02408f6b 100644 --- a/crates/turborepo-lib/src/query/task.rs +++ b/crates/turborepo-lib/src/query/task.rs @@ -2,11 +2,12 @@ use std::sync::Arc; use async_graphql::Object; use turborepo_errors::Spanned; +use turborepo_task_id::TaskId; use crate::{ engine::TaskNode, query::{package::Package, Array, Error}, - run::{task_id::TaskId, Run}, + run::Run, }; pub struct RepositoryTask { diff --git a/crates/turborepo-lib/src/run/builder.rs b/crates/turborepo-lib/src/run/builder.rs index eb52c31c903c5..08b9e0e8e50fb 100644 --- a/crates/turborepo-lib/src/run/builder.rs +++ b/crates/turborepo-lib/src/run/builder.rs @@ -22,6 +22,7 @@ use turborepo_repository::{ }; use turborepo_scm::SCM; use turborepo_signals::{SignalHandler, SignalSubscriber}; +use turborepo_task_id::TaskName; use turborepo_telemetry::events::{ command::CommandEventBuilder, generic::{DaemonInitStatus, GenericEventBuilder}, @@ -42,12 +43,13 @@ use { use crate::{ cli::DryRunMode, commands::CommandBase, + config::resolve_turbo_config_path, engine::{Engine, EngineBuilder}, microfrontends::MicrofrontendsConfigs, opts::Opts, - run::{scope, task_access::TaskAccess, task_id::TaskName, Error, Run, RunCache}, + run::{scope, task_access::TaskAccess, Error, Run, RunCache}, shim::TurboState, - turbo_json::{resolve_turbo_config_path, TurboJson, TurboJsonLoader, UIMode}, + turbo_json::{TurboJson, TurboJsonLoader, UIMode}, DaemonConnector, }; diff --git a/crates/turborepo-lib/src/run/cache.rs b/crates/turborepo-lib/src/run/cache.rs index 8289be226e601..8e3eee1cc9a8c 100644 --- a/crates/turborepo-lib/src/run/cache.rs +++ b/crates/turborepo-lib/src/run/cache.rs @@ -15,6 +15,7 @@ use turborepo_cache::{ }; use turborepo_repository::package_graph::PackageInfo; use turborepo_scm::SCM; +use turborepo_task_id::TaskId; use turborepo_telemetry::events::{task::PackageTaskEventBuilder, TrackedErrors}; use turborepo_ui::{color, tui::event::CacheResult, ColorConfig, ColorSelector, LogWriter, GREY}; @@ -23,7 +24,6 @@ use crate::{ daemon::{DaemonClient, DaemonConnector}, hash::{FileHashes, TurboHash}, opts::RunCacheOpts, - run::task_id::TaskId, task_graph::{TaskDefinition, TaskOutputs}, }; diff --git a/crates/turborepo-lib/src/run/mod.rs b/crates/turborepo-lib/src/run/mod.rs index cc4eff8530bba..1ca76233273bf 100644 --- a/crates/turborepo-lib/src/run/mod.rs +++ b/crates/turborepo-lib/src/run/mod.rs @@ -9,7 +9,6 @@ pub(crate) mod package_discovery; pub(crate) mod scope; pub(crate) mod summary; pub mod task_access; -pub mod task_id; mod ui; pub mod watch; diff --git a/crates/turborepo-lib/src/run/summary/execution.rs b/crates/turborepo-lib/src/run/summary/execution.rs index 389bf9d05a9da..c8ba1d042c9bf 100644 --- a/crates/turborepo-lib/src/run/summary/execution.rs +++ b/crates/turborepo-lib/src/run/summary/execution.rs @@ -4,10 +4,11 @@ use chrono::{DateTime, Local}; use serde::Serialize; use tokio::sync::mpsc; use turbopath::{AbsoluteSystemPathBuf, AnchoredSystemPath}; +use turborepo_task_id::TaskId; use turborepo_ui::{color, cprintln, ColorConfig, BOLD, BOLD_GREEN, BOLD_RED, MAGENTA, YELLOW}; use super::TurboDuration; -use crate::run::{summary::task::TaskSummary, task_id::TaskId}; +use crate::run::summary::task::TaskSummary; // Just used to make changing the type that gets passed to the state management // thread easy diff --git a/crates/turborepo-lib/src/run/summary/mod.rs b/crates/turborepo-lib/src/run/summary/mod.rs index 7261be7624a59..c6f82ab7547af 100644 --- a/crates/turborepo-lib/src/run/summary/mod.rs +++ b/crates/turborepo-lib/src/run/summary/mod.rs @@ -26,12 +26,12 @@ use turbopath::{AbsoluteSystemPath, AbsoluteSystemPathBuf, AnchoredSystemPath}; use turborepo_env::EnvironmentVariableMap; use turborepo_repository::package_graph::{PackageGraph, PackageName}; use turborepo_scm::SCM; +use turborepo_task_id::TaskId; use turborepo_ui::{color, cprintln, cwriteln, ColorConfig, BOLD, BOLD_CYAN, GREY}; use self::{ execution::TaskState, task::SinglePackageTaskSummary, task_factory::TaskSummaryFactory, }; -use super::task_id::TaskId; use crate::{ cli, cli::{DryRunMode, EnvMode}, diff --git a/crates/turborepo-lib/src/run/summary/task.rs b/crates/turborepo-lib/src/run/summary/task.rs index 6c57b97db23a8..4ed0d4e688ae6 100644 --- a/crates/turborepo-lib/src/run/summary/task.rs +++ b/crates/turborepo-lib/src/run/summary/task.rs @@ -4,11 +4,11 @@ use serde::Serialize; use turbopath::{AnchoredSystemPathBuf, RelativeUnixPathBuf}; use turborepo_cache::CacheHitMetadata; use turborepo_env::{DetailedMap, EnvironmentVariableMap}; +use turborepo_task_id::TaskId; use super::{execution::TaskExecutionSummary, EnvMode}; use crate::{ cli::OutputLogsMode, - run::task_id::TaskId, task_graph::{TaskDefinition, TaskOutputs}, }; diff --git a/crates/turborepo-lib/src/run/summary/task_factory.rs b/crates/turborepo-lib/src/run/summary/task_factory.rs index fcd237538ed33..b1a5fdf4f7617 100644 --- a/crates/turborepo-lib/src/run/summary/task_factory.rs +++ b/crates/turborepo-lib/src/run/summary/task_factory.rs @@ -2,6 +2,7 @@ use std::collections::HashSet; use turborepo_env::EnvironmentVariableMap; use turborepo_repository::package_graph::{PackageGraph, PackageInfo, PackageName}; +use turborepo_task_id::TaskId; use super::{ execution::TaskExecutionSummary, @@ -12,7 +13,6 @@ use crate::{ cli, engine::{Engine, TaskNode}, opts::RunOpts, - run::task_id::TaskId, task_graph::TaskDefinition, task_hash::{get_external_deps_hash, TaskHashTracker}, }; diff --git a/crates/turborepo-lib/src/run/watch.rs b/crates/turborepo-lib/src/run/watch.rs index b9807bc3d99a3..6e689acf80f5b 100644 --- a/crates/turborepo-lib/src/run/watch.rs +++ b/crates/turborepo-lib/src/run/watch.rs @@ -16,10 +16,10 @@ use turborepo_ui::sender::UISender; use crate::{ commands::CommandBase, + config::resolve_turbo_config_path, daemon::{proto, DaemonConnectorError, DaemonError}, get_version, opts, run::{self, builder::RunBuilder, scope::target_selector::InvalidSelectorError, Run}, - turbo_json::resolve_turbo_config_path, DaemonConnector, DaemonPaths, }; diff --git a/crates/turborepo-lib/src/task_graph/mod.rs b/crates/turborepo-lib/src/task_graph/mod.rs index 0f3b361b8c621..d97fb128fefc1 100644 --- a/crates/turborepo-lib/src/task_graph/mod.rs +++ b/crates/turborepo-lib/src/task_graph/mod.rs @@ -6,23 +6,21 @@ use globwalk::{GlobError, ValidatedGlob}; use serde::{Deserialize, Serialize}; use turbopath::{AnchoredSystemPath, AnchoredSystemPathBuf, RelativeUnixPathBuf}; use turborepo_errors::Spanned; +use turborepo_task_id::{TaskId, TaskName}; pub use visitor::{Error as VisitorError, Visitor}; -use crate::{ - cli::{EnvMode, OutputLogsMode}, - run::task_id::{TaskId, TaskName}, -}; +use crate::cli::{EnvMode, OutputLogsMode}; // Constructed from a RawTaskDefinition #[derive(Debug, PartialEq, Clone, Eq)] pub struct TaskDefinition { pub outputs: TaskOutputs, - pub(crate) cache: bool, + pub cache: bool, // This field is custom-marshalled from `env` and `depends_on`` - pub(crate) env: Vec, + pub env: Vec, - pub(crate) pass_through_env: Option>, + pub pass_through_env: Option>, // TopologicalDependencies are tasks from package dependencies. // E.g. "build" is a topological dependency in: diff --git a/crates/turborepo-lib/src/task_graph/visitor/command.rs b/crates/turborepo-lib/src/task_graph/visitor/command.rs index 948e519716194..0b3cb4d74dd25 100644 --- a/crates/turborepo-lib/src/task_graph/visitor/command.rs +++ b/crates/turborepo-lib/src/task_graph/visitor/command.rs @@ -12,9 +12,10 @@ use turborepo_repository::{ package_graph::{PackageGraph, PackageInfo, PackageName}, package_manager::PackageManager, }; +use turborepo_task_id::TaskId; use super::Error; -use crate::{microfrontends::MicrofrontendsConfigs, opts::TaskArgs, run::task_id::TaskId}; +use crate::{microfrontends::MicrofrontendsConfigs, opts::TaskArgs}; pub trait CommandProvider { fn command( diff --git a/crates/turborepo-lib/src/task_graph/visitor/exec.rs b/crates/turborepo-lib/src/task_graph/visitor/exec.rs index 9cfffcaafa815..230dff346c275 100644 --- a/crates/turborepo-lib/src/task_graph/visitor/exec.rs +++ b/crates/turborepo-lib/src/task_graph/visitor/exec.rs @@ -10,6 +10,7 @@ use tracing::{error, Instrument}; use turborepo_env::{platform::PlatformEnv, EnvironmentVariableMap}; use turborepo_process::{ChildExit, Command, ProcessManager}; use turborepo_repository::package_manager::PackageManager; +use turborepo_task_id::TaskId; use turborepo_telemetry::events::{task::PackageTaskEventBuilder, TrackedErrors}; use turborepo_ui::{ColorConfig, OutputWriter}; @@ -23,7 +24,7 @@ use crate::{ cli::ContinueMode, config::UIMode, engine::{Engine, StopExecution}, - run::{summary::TaskTracker, task_access::TaskAccess, task_id::TaskId, CacheOutput, TaskCache}, + run::{summary::TaskTracker, task_access::TaskAccess, CacheOutput, TaskCache}, task_hash::TaskHashTracker, }; diff --git a/crates/turborepo-lib/src/task_graph/visitor/mod.rs b/crates/turborepo-lib/src/task_graph/visitor/mod.rs index d4ba7d3ffdf26..25771f79e6962 100644 --- a/crates/turborepo-lib/src/task_graph/visitor/mod.rs +++ b/crates/turborepo-lib/src/task_graph/visitor/mod.rs @@ -26,6 +26,7 @@ use turborepo_env::{platform::PlatformEnv, EnvironmentVariableMap}; use turborepo_errors::TURBO_SITE; use turborepo_process::ProcessManager; use turborepo_repository::package_graph::{PackageGraph, PackageName, ROOT_PKG_NAME}; +use turborepo_task_id::TaskId; use turborepo_telemetry::events::{ generic::GenericEventBuilder, task::PackageTaskEventBuilder, EventBuilder, TrackedErrors, }; @@ -42,7 +43,6 @@ use crate::{ global_hash::GlobalHashableInputs, summary::{self, GlobalHashSummary, RunTracker}, task_access::TaskAccess, - task_id::TaskId, RunCache, }, task_hash::{self, PackageInputsHashes, TaskHashTrackerState, TaskHasher}, diff --git a/crates/turborepo-lib/src/task_hash.rs b/crates/turborepo-lib/src/task_hash.rs index 30c179a3dea95..fcf82ffd936da 100644 --- a/crates/turborepo-lib/src/task_hash.rs +++ b/crates/turborepo-lib/src/task_hash.rs @@ -13,6 +13,7 @@ use turborepo_env::{BySource, DetailedMap, EnvironmentVariableMap}; use turborepo_frameworks::{infer_framework, Slug as FrameworkSlug}; use turborepo_repository::package_graph::{PackageInfo, PackageName}; use turborepo_scm::SCM; +use turborepo_task_id::TaskId; use turborepo_telemetry::events::{ generic::GenericEventBuilder, task::PackageTaskEventBuilder, EventBuilder, }; @@ -22,7 +23,6 @@ use crate::{ engine::TaskNode, hash::{FileHashes, LockFilePackages, TaskHashable, TurboHash}, opts::RunOpts, - run::task_id::TaskId, task_graph::TaskDefinition, DaemonClient, DaemonConnector, }; diff --git a/crates/turborepo-lib/src/turbo_json/ARCHITECTURE.md b/crates/turborepo-lib/src/turbo_json/ARCHITECTURE.md new file mode 100644 index 0000000000000..9728055952ae4 --- /dev/null +++ b/crates/turborepo-lib/src/turbo_json/ARCHITECTURE.md @@ -0,0 +1,68 @@ +# Turborepo Architecture: Configuration and Task Definition Loading + +This document explains the process of loading `turbo.json` files and creating task definitions in Turborepo. +The process involves several phases that transform raw configuration files into an executable task graph. +The loading and resolving of task definitions is driven during task graph construction. + +## Overview + +The configuration and task loading process follows this high-level flow: + +1. **Configuration Resolution**: Collect and merge configuration from multiple sources +2. **TurboJson Loading**: Resolve `turbo.json` files, these are usually files on disk, but can be synthesized +3. **Task Definition Resolution**: Convert raw task definitions into validated structures. `extends` is handled in this step +4. **Task Graph Construction**: Build the executable task graph from resolved definitions + +## Phase 1: Configuration Resolution + +### Sources and Priority + +Configuration is collected from multiple sources with the following priority (highest to lowest): + +1. Command line arguments +2. Environment variables +3. Override environment variables +4. Local configuration (`.turbo/config.json`) +5. Global authentication (`~/.turbo/auth.json`) +6. Global configuration (`~/.turbo/config.json`) +7. Turbo.json configuration + +### Key Components + +- **`TurborepoConfigBuilder`** (`crates/turborepo-lib/src/config/mod.rs`): Orchestrates the configuration loading process +- **`TurboJsonReader`** (`crates/turborepo-lib/src/config/turbo_json.rs`): Extracts configuration options from the root `turbo.json` file +- **`ConfigurationOptions`**: The final merged configuration structure + +## Phase 2: TurboJson Loading + +### Key Components + +- **`TurboJsonLoader`** (`crates/turborepo-lib/src/turbo_json/loader.rs`): Loads and resolves turbo.json files +- **`RawTurboJson`**: Raw deserialized structure from JSON files +- **`TurboJson`**: Resolved and validated structure, all DSL magic strings have been handled + +### Process + +1. **File Discovery**: Locate `turbo.json` or `turbo.jsonc` files +2. **Parsing**: Deserialize JSON into `RawTurboJson` structures +3. **Validation**: Convert to `TurboJson` with validation rules +4. **Workspace Resolution**: Apply workspace-specific overrides + +## Phase 3: Task Definition Resolution + +### Key Components + +- **`RawTaskDefinition`**: Raw task configuration from JSON +- **`TaskDefinition`**: Validated and processed task configuration +- **`TaskId`** and **`TaskName`** (from `turborepo-task-id` crate): Task identification types + +### Transformation Process + +Raw task definitions undergo several transformations: + +1. **Path Resolution**: Convert relative paths and handle `$TURBO_ROOT$` tokens +2. **Dependency Parsing**: Parse `dependsOn` into topological and task dependencies +3. **Environment Variable Collection**: Extract `env` and `passThroughEnv` variables +4. **Output Processing**: Handle inclusion/exclusion patterns in outputs +5. **Inheritance**: Handle merging multiple `RawTaskDefinition`s into a single usable task definition +6. **Validation**: Ensure configuration consistency (e.g., interactive tasks can't be cached) diff --git a/crates/turborepo-lib/src/turbo_json/extend.rs b/crates/turborepo-lib/src/turbo_json/extend.rs new file mode 100644 index 0000000000000..4783aff452a14 --- /dev/null +++ b/crates/turborepo-lib/src/turbo_json/extend.rs @@ -0,0 +1,229 @@ +//! Module for code related to "extends" behavior for task definitions + +use super::RawTaskDefinition; + +impl FromIterator for RawTaskDefinition { + fn from_iter>(iter: T) -> Self { + iter.into_iter() + .fold(RawTaskDefinition::default(), |mut def, other| { + def.merge(other); + def + }) + } +} + +macro_rules! set_field { + ($this:ident, $other:ident, $field:ident) => {{ + if let Some(field) = $other.$field { + $this.$field = field.into(); + } + }}; +} + +impl RawTaskDefinition { + // Merges another RawTaskDefinition into this one + // By default any fields present on `other` will override present fields. + pub fn merge(&mut self, other: RawTaskDefinition) { + set_field!(self, other, outputs); + + let other_has_range = other.cache.as_ref().is_some_and(|c| c.range.is_some()); + let self_does_not_have_range = self.cache.as_ref().is_some_and(|c| c.range.is_none()); + + if other.cache.is_some() + // If other has range info and we're missing it, carry it over + || (other_has_range && self_does_not_have_range) + { + self.cache = other.cache; + } + set_field!(self, other, depends_on); + set_field!(self, other, inputs); + set_field!(self, other, output_logs); + set_field!(self, other, persistent); + set_field!(self, other, interruptible); + set_field!(self, other, env); + set_field!(self, other, pass_through_env); + set_field!(self, other, interactive); + set_field!(self, other, env_mode); + set_field!(self, other, with); + } +} + +#[cfg(test)] +mod test { + use turborepo_errors::Spanned; + use turborepo_unescape::UnescapedString; + + use super::*; + use crate::cli::OutputLogsMode; + + // Shared test fixtures + fn create_base_task() -> RawTaskDefinition { + RawTaskDefinition { + cache: Some(Spanned::new(true)), + persistent: Some(Spanned::new(false)), + outputs: Some(vec![Spanned::new(UnescapedString::from("dist/**"))]), + inputs: Some(vec![Spanned::new(UnescapedString::from("src/**"))]), + env: Some(vec![Spanned::new(UnescapedString::from("NODE_ENV"))]), + ..Default::default() + } + } + + fn create_override_task() -> RawTaskDefinition { + RawTaskDefinition { + cache: Some(Spanned::new(false)), + persistent: Some(Spanned::new(true)), + outputs: Some(vec![Spanned::new(UnescapedString::from("build/**"))]), + inputs: Some(vec![Spanned::new(UnescapedString::from("lib/**"))]), + env: Some(vec![Spanned::new(UnescapedString::from("PROD_ENV"))]), + output_logs: Some(Spanned::new(OutputLogsMode::Full)), + interruptible: Some(Spanned::new(true)), + ..Default::default() + } + } + + fn create_partial_task() -> RawTaskDefinition { + RawTaskDefinition { + persistent: Some(Spanned::new(true)), + output_logs: Some(Spanned::new(OutputLogsMode::HashOnly)), + ..Default::default() + } + } + + #[test] + fn test_other_takes_priority() { + let mut base = create_base_task(); + let override_task = create_override_task(); + + // Store original values for comparison + let original_cache = base.cache.clone(); + let original_persistent = base.persistent.clone(); + let original_outputs = base.outputs.clone(); + + // Perform merge + base.merge(override_task.clone()); + + // All fields from override_task should take priority + assert_eq!(base.cache, override_task.cache); + assert_eq!(base.persistent, override_task.persistent); + assert_eq!(base.outputs, override_task.outputs); + assert_eq!(base.inputs, override_task.inputs); + assert_eq!(base.env, override_task.env); + assert_eq!(base.output_logs, override_task.output_logs); + assert_eq!(base.interruptible, override_task.interruptible); + + // Verify values actually changed + assert_ne!(base.cache, original_cache); + assert_ne!(base.persistent, original_persistent); + assert_ne!(base.outputs, original_outputs); + } + + #[test] + fn test_partial_merge_preserves_existing() { + let mut base = create_base_task(); + let partial = create_partial_task(); + + // Store original values that should be preserved + let original_cache = base.cache.clone(); + let original_outputs = base.outputs.clone(); + let original_inputs = base.inputs.clone(); + let original_env = base.env.clone(); + + // Perform merge + base.merge(partial.clone()); + + // Fields present in partial should be overridden + assert_eq!(base.persistent, partial.persistent); + assert_eq!(base.output_logs, partial.output_logs); + + // Fields not present in partial should be preserved + assert_eq!(base.cache, original_cache); + assert_eq!(base.outputs, original_outputs); + assert_eq!(base.inputs, original_inputs); + assert_eq!(base.env, original_env); + + // Fields not set in either should remain None + assert_eq!(base.interruptible, None); + assert_eq!(base.interactive, None); + } + + #[test] + fn test_from_iter_last_takes_priority() { + let first = create_base_task(); + let second = create_partial_task(); + let third = create_override_task(); + + let tasks = vec![first.clone(), second.clone(), third.clone()]; + let result: RawTaskDefinition = tasks.into_iter().collect(); + + // Fields present in the last task (third) should take priority + assert_eq!(result.cache, third.cache); + assert_eq!(result.persistent, third.persistent); + assert_eq!(result.outputs, third.outputs); + assert_eq!(result.inputs, third.inputs); + assert_eq!(result.env, third.env); + assert_eq!(result.output_logs, third.output_logs); + assert_eq!(result.interruptible, third.interruptible); + + // Fields only present in earlier tasks should be preserved if not + // overridden (none in this case since third task overrides all + // fields that first task had) + } + + #[test] + fn test_from_iter_combines_across_multiple_tasks() { + let first = RawTaskDefinition { + cache: Some(Spanned::new(true)), + outputs: Some(vec![Spanned::new(UnescapedString::from("dist/**"))]), + ..Default::default() + }; + + let second = RawTaskDefinition { + persistent: Some(Spanned::new(false)), + inputs: Some(vec![Spanned::new(UnescapedString::from("src/**"))]), + ..Default::default() + }; + + let third = RawTaskDefinition { + env: Some(vec![Spanned::new(UnescapedString::from("NODE_ENV"))]), + output_logs: Some(Spanned::new(OutputLogsMode::Full)), + // Override cache from first task + cache: Some(Spanned::new(false)), + ..Default::default() + }; + + let tasks = vec![first.clone(), second.clone(), third.clone()]; + let result: RawTaskDefinition = tasks.into_iter().collect(); + + // Last task's cache should override first task's cache + assert_eq!(result.cache, third.cache); + + // Fields from second task should be preserved since not overridden + assert_eq!(result.persistent, second.persistent); + assert_eq!(result.inputs, second.inputs); + + // Fields from first task should be preserved since not overridden later + assert_eq!(result.outputs, first.outputs); + + // Fields from third task + assert_eq!(result.env, third.env); + assert_eq!(result.output_logs, third.output_logs); + } + + #[test] + fn test_from_iter_empty_iterator() { + let empty_vec: Vec = vec![]; + let result: RawTaskDefinition = empty_vec.into_iter().collect(); + + // Should be equivalent to default + assert_eq!(result, RawTaskDefinition::default()); + } + + #[test] + fn test_from_iter_single_task() { + let single_task = create_base_task(); + let tasks = vec![single_task.clone()]; + let result: RawTaskDefinition = tasks.into_iter().collect(); + + assert_eq!(result, single_task); + } +} diff --git a/crates/turborepo-lib/src/turbo_json/loader.rs b/crates/turborepo-lib/src/turbo_json/loader.rs index 69c12074c3330..decd21140f76f 100644 --- a/crates/turborepo-lib/src/turbo_json/loader.rs +++ b/crates/turborepo-lib/src/turbo_json/loader.rs @@ -8,13 +8,14 @@ use turborepo_repository::{ package_graph::{PackageInfo, PackageName}, package_json::PackageJson, }; +use turborepo_task_id::TaskName; -use super::{Pipeline, RawTaskDefinition, TurboJson, CONFIG_FILE, CONFIG_FILE_JSONC}; +use super::{Pipeline, RawTaskDefinition, TurboJson}; use crate::{ cli::EnvMode, - config::Error, + config::{Error, CONFIG_FILE, CONFIG_FILE_JSONC}, microfrontends::MicrofrontendsConfigs, - run::{task_access::TASK_ACCESS_CONFIG_PATH, task_id::TaskName}, + run::task_access::TASK_ACCESS_CONFIG_PATH, }; /// Structure for loading TurboJson structures. diff --git a/crates/turborepo-lib/src/turbo_json/mod.rs b/crates/turborepo-lib/src/turbo_json/mod.rs index c4bed46446a02..88576494ccb4b 100644 --- a/crates/turborepo-lib/src/turbo_json/mod.rs +++ b/crates/turborepo-lib/src/turbo_json/mod.rs @@ -14,18 +14,17 @@ use struct_iterable::Iterable; use turbopath::{AbsoluteSystemPath, RelativeUnixPath}; use turborepo_errors::Spanned; use turborepo_repository::package_graph::ROOT_PKG_NAME; +use turborepo_task_id::{TaskId, TaskName}; use turborepo_unescape::UnescapedString; use crate::{ cli::{EnvMode, OutputLogsMode}, - config::{ConfigurationOptions, Error, InvalidEnvPrefixError}, - run::{ - task_access::TaskAccessTraceFile, - task_id::{TaskId, TaskName}, - }, + config::{Error, InvalidEnvPrefixError}, + run::task_access::TaskAccessTraceFile, task_graph::{TaskDefinition, TaskInputs, TaskOutputs}, }; +mod extend; mod loader; pub mod parser; @@ -37,37 +36,9 @@ const TURBO_ROOT: &str = "$TURBO_ROOT$"; const TURBO_ROOT_SLASH: &str = "$TURBO_ROOT$/"; pub const TURBO_DEFAULT: &str = "$TURBO_DEFAULT$"; -pub const CONFIG_FILE: &str = "turbo.json"; -pub const CONFIG_FILE_JSONC: &str = "turbo.jsonc"; const ENV_PIPELINE_DELIMITER: &str = "$"; const TOPOLOGICAL_PIPELINE_DELIMITER: &str = "^"; -/// Given a directory path, determines which turbo.json configuration file to -/// use. Returns an error if both turbo.json and turbo.jsonc exist in the same -/// directory. Returns the path to the config file to use, defaulting to -/// turbo.json if neither exists. -pub fn resolve_turbo_config_path( - dir_path: &turbopath::AbsoluteSystemPath, -) -> Result { - use crate::config::Error; - - let turbo_json_path = dir_path.join_component(CONFIG_FILE); - let turbo_jsonc_path = dir_path.join_component(CONFIG_FILE_JSONC); - - let turbo_json_exists = turbo_json_path.try_exists()?; - let turbo_jsonc_exists = turbo_jsonc_path.try_exists()?; - - match (turbo_json_exists, turbo_jsonc_exists) { - (true, true) => Err(Error::MultipleTurboConfigs { - directory: dir_path.to_string(), - }), - (true, false) => Ok(turbo_json_path), - (false, true) => Ok(turbo_jsonc_path), - // Default to turbo.json if neither exists - (false, false) => Ok(turbo_json_path), - } -} - #[derive(Serialize, Deserialize, Debug, Default, PartialEq, Clone, Deserializable)] #[serde(rename_all = "camelCase")] pub struct SpacesJson { @@ -100,57 +71,25 @@ pub struct TurboJson { // Iterable is required to enumerate allowed keys #[derive(Clone, Debug, Default, Iterable, Serialize, Deserializable)] #[serde(rename_all = "camelCase")] -pub(crate) struct RawRemoteCacheOptions { +pub struct RawRemoteCacheOptions { #[serde(skip_serializing_if = "Option::is_none")] - api_url: Option>, + pub api_url: Option>, #[serde(skip_serializing_if = "Option::is_none")] - login_url: Option>, + pub login_url: Option>, #[serde(skip_serializing_if = "Option::is_none")] - team_slug: Option>, + pub team_slug: Option>, #[serde(skip_serializing_if = "Option::is_none")] - team_id: Option>, + pub team_id: Option>, #[serde(skip_serializing_if = "Option::is_none")] - signature: Option>, + pub signature: Option>, #[serde(skip_serializing_if = "Option::is_none")] - preflight: Option>, + pub preflight: Option>, #[serde(skip_serializing_if = "Option::is_none")] - timeout: Option>, + pub timeout: Option>, #[serde(skip_serializing_if = "Option::is_none")] - enabled: Option>, + pub enabled: Option>, #[serde(skip_serializing_if = "Option::is_none")] - upload_timeout: Option>, -} - -impl From<&RawRemoteCacheOptions> for ConfigurationOptions { - fn from(remote_cache_opts: &RawRemoteCacheOptions) -> Self { - Self { - api_url: remote_cache_opts - .api_url - .as_ref() - .map(|s| s.as_inner().clone()), - login_url: remote_cache_opts - .login_url - .as_ref() - .map(|s| s.as_inner().clone()), - team_slug: remote_cache_opts - .team_slug - .as_ref() - .map(|s| s.as_inner().clone()), - team_id: remote_cache_opts - .team_id - .as_ref() - .map(|s| s.as_inner().clone()), - signature: remote_cache_opts.signature.as_ref().map(|s| *s.as_inner()), - preflight: remote_cache_opts.preflight.as_ref().map(|s| *s.as_inner()), - timeout: remote_cache_opts.timeout.as_ref().map(|s| *s.as_inner()), - upload_timeout: remote_cache_opts - .upload_timeout - .as_ref() - .map(|s| *s.as_inner()), - enabled: remote_cache_opts.enabled.as_ref().map(|s| *s.as_inner()), - ..Self::default() - } - } + pub upload_timeout: Option>, } #[derive(Serialize, Default, Debug, Clone, Iterable, Deserializable)] @@ -184,7 +123,7 @@ pub struct RawTurboJson { pub pipeline: Option>, // Configuration options when interfacing with the remote cache #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) remote_cache: Option, + pub remote_cache: Option, #[serde(skip_serializing_if = "Option::is_none", rename = "ui")] pub ui: Option>, #[serde( @@ -323,42 +262,6 @@ pub struct RawTaskDefinition { with: Option>>, } -macro_rules! set_field { - ($this:ident, $other:ident, $field:ident) => {{ - if let Some(field) = $other.$field { - $this.$field = field.into(); - } - }}; -} - -impl RawTaskDefinition { - // merge accepts a RawTaskDefinition and - // merges it into RawTaskDefinition. - pub fn merge(&mut self, other: RawTaskDefinition) { - set_field!(self, other, outputs); - - let other_has_range = other.cache.as_ref().is_some_and(|c| c.range.is_some()); - let self_does_not_have_range = self.cache.as_ref().is_some_and(|c| c.range.is_none()); - - if other.cache.is_some() - // If other has range info and we're missing it, carry it over - || (other_has_range && self_does_not_have_range) - { - self.cache = other.cache; - } - set_field!(self, other, depends_on); - set_field!(self, other, inputs); - set_field!(self, other, output_logs); - set_field!(self, other, persistent); - set_field!(self, other, interruptible); - set_field!(self, other, env); - set_field!(self, other, pass_through_env); - set_field!(self, other, interactive); - set_field!(self, other, env_mode); - set_field!(self, other, with); - } -} - impl TryFrom>> for TaskOutputs { type Error = Error; fn try_from(outputs: Vec>) -> Result { @@ -401,16 +304,6 @@ impl TryFrom>> for TaskOutputs { } } -impl FromIterator for RawTaskDefinition { - fn from_iter>(iter: T) -> Self { - iter.into_iter() - .fold(RawTaskDefinition::default(), |mut def, other| { - def.merge(other); - def - }) - } -} - impl TryFrom>>> for TaskInputs { type Error = Error; @@ -950,6 +843,7 @@ mod tests { use serde_json::json; use test_case::test_case; use turbopath::RelativeUnixPath; + use turborepo_task_id::TaskName; use turborepo_unescape::UnescapedString; use super::{ @@ -959,7 +853,6 @@ mod tests { use crate::{ boundaries::BoundariesConfig, cli::OutputLogsMode, - run::task_id::TaskName, task_graph::{TaskDefinition, TaskOutputs}, turbo_json::RawTaskDefinition, }; diff --git a/crates/turborepo-lib/src/turbo_json/parser.rs b/crates/turborepo-lib/src/turbo_json/parser.rs index 402111eb17dab..86bf53bc93945 100644 --- a/crates/turborepo-lib/src/turbo_json/parser.rs +++ b/crates/turborepo-lib/src/turbo_json/parser.rs @@ -13,11 +13,11 @@ use struct_iterable::Iterable; use thiserror::Error; use tracing::log::warn; use turborepo_errors::{ParseDiagnostic, WithMetadata}; +use turborepo_task_id::TaskName; use turborepo_unescape::UnescapedString; use crate::{ boundaries::{BoundariesConfig, Permissions, Rule}, - run::task_id::TaskName, turbo_json::{Pipeline, RawRemoteCacheOptions, RawTaskDefinition, RawTurboJson, Spanned}, }; @@ -45,18 +45,6 @@ fn create_unknown_key_diagnostic_from_struct( DeserializationDiagnostic::new_unknown_key(unknown_key, range, &allowed_keys_borrowed) } -impl Deserializable for TaskName<'static> { - fn deserialize( - value: &impl DeserializableValue, - name: &str, - diagnostics: &mut Vec, - ) -> Option { - let task_id: String = UnescapedString::deserialize(value, name, diagnostics)?.into(); - - Some(Self::from(task_id)) - } -} - impl Deserializable for Pipeline { fn deserialize( value: &impl DeserializableValue, @@ -84,7 +72,11 @@ impl DeserializationVisitor for PipelineVisitor { let mut result = BTreeMap::new(); for (key, value) in members.flatten() { let task_name_range = value.range(); - let task_name = TaskName::deserialize(&key, "", diagnostics)?; + let task_name = TaskName::from(String::from(UnescapedString::deserialize( + &key, + "", + diagnostics, + )?)); let task_name_start: usize = task_name_range.start().into(); let task_name_end: usize = task_name_range.end().into(); result.insert( diff --git a/crates/turborepo-task-id/Cargo.toml b/crates/turborepo-task-id/Cargo.toml new file mode 100644 index 0000000000000..2e98bd4d22b48 --- /dev/null +++ b/crates/turborepo-task-id/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "turborepo-task-id" +version = "0.1.0" +edition = "2021" +license = "MIT" + +[lints] +workspace = true + +[dependencies] +serde = { workspace = true, features = ["derive"] } +thiserror = { workspace = true } +turborepo-repository = { workspace = true } + +[dev-dependencies] +test-case = { workspace = true } diff --git a/crates/turborepo-lib/src/run/task_id.rs b/crates/turborepo-task-id/src/lib.rs similarity index 100% rename from crates/turborepo-lib/src/run/task_id.rs rename to crates/turborepo-task-id/src/lib.rs