diff --git a/crates/turborepo-lib/src/config/env.rs b/crates/turborepo-lib/src/config/env.rs index 99d9c9cbe6bc1..c33fd42504a67 100644 --- a/crates/turborepo-lib/src/config/env.rs +++ b/crates/turborepo-lib/src/config/env.rs @@ -44,6 +44,7 @@ const TURBO_MAPPING: &[(&str, &str)] = [ ("turbo_cache", "cache"), ("turbo_tui_scrollback_length", "tui_scrollback_length"), ("turbo_concurrency", "concurrency"), + ("turbo_no_update_notifier", "no_update_notifier"), ] .as_slice(); @@ -166,6 +167,8 @@ impl ResolvedConfigurationOptions for EnvVars { let allow_no_package_manager = self.truthy_value("allow_no_package_manager").flatten(); + let no_update_notifier = self.truthy_value("no_update_notifier").flatten(); + // Process daemon let daemon = self.truthy_value("daemon").flatten(); @@ -231,6 +234,7 @@ impl ResolvedConfigurationOptions for EnvVars { remote_cache_read_only, run_summary, allow_no_turbo_json, + no_update_notifier, // Processed numbers timeout, diff --git a/crates/turborepo-lib/src/config/mod.rs b/crates/turborepo-lib/src/config/mod.rs index 02d5f88719e22..8ed37957b3621 100644 --- a/crates/turborepo-lib/src/config/mod.rs +++ b/crates/turborepo-lib/src/config/mod.rs @@ -298,6 +298,7 @@ pub struct ConfigurationOptions { pub(crate) allow_no_turbo_json: Option, pub(crate) tui_scrollback_length: Option, pub(crate) concurrency: Option, + pub(crate) no_update_notifier: Option, } #[derive(Default)] @@ -463,6 +464,10 @@ impl ConfigurationOptions { pub fn allow_no_turbo_json(&self) -> bool { self.allow_no_turbo_json.unwrap_or_default() } + + pub fn no_update_notifier(&self) -> bool { + self.no_update_notifier.unwrap_or_default() + } } // Maps Some("") to None to emulate how Go handles empty strings diff --git a/crates/turborepo-lib/src/shim/mod.rs b/crates/turborepo-lib/src/shim/mod.rs index 6ec56fff02f4c..e7e7136d38c5a 100644 --- a/crates/turborepo-lib/src/shim/mod.rs +++ b/crates/turborepo-lib/src/shim/mod.rs @@ -39,6 +39,16 @@ pub enum Error { #[label = "Requires a path to be passed after it"] flag_range: SourceSpan, }, + #[error("No value assigned to `--root-turbo-json` flag.")] + #[diagnostic(code(turbo::shim::empty_root_turbo_json))] + EmptyRootTurboJson { + #[backtrace] + backtrace: Backtrace, + #[source_code] + args_string: String, + #[label = "Requires a path to be passed after it"] + flag_range: SourceSpan, + }, #[error(transparent)] #[diagnostic(transparent)] Cli(#[from] cli::Error), @@ -75,7 +85,12 @@ fn run_correct_turbo( ui: ColorConfig, ) -> Result { if let Some(turbo_state) = LocalTurboState::infer(&repo_state.root) { - try_check_for_updates(&shim_args, turbo_state.version()); + let mut builder = crate::config::TurborepoConfigBuilder::new(&repo_state.root); + if let Some(root_turbo_json) = &shim_args.root_turbo_json { + builder = builder.with_root_turbo_json_path(Some(root_turbo_json.clone())); + } + let config = builder.build().unwrap_or_default(); + try_check_for_updates(&shim_args, turbo_state.version(), &config); if turbo_state.local_is_self() { env::set_var( @@ -95,7 +110,12 @@ fn run_correct_turbo( spawn_npx_turbo(&repo_state, local_config.turbo_version(), shim_args) } else { let version = get_version(); - try_check_for_updates(&shim_args, version); + let mut builder = crate::config::TurborepoConfigBuilder::new(&repo_state.root); + if let Some(root_turbo_json) = &shim_args.root_turbo_json { + builder = builder.with_root_turbo_json_path(Some(root_turbo_json.clone())); + } + let config = builder.build().unwrap_or_default(); + try_check_for_updates(&shim_args, version, &config); // cli::run checks for this env var, rather than an arg, so that we can support // calling old versions without passing unknown flags. env::set_var( @@ -251,8 +271,12 @@ fn is_turbo_binary_path_set() -> bool { env::var("TURBO_BINARY_PATH").is_ok() } -fn try_check_for_updates(args: &ShimArgs, current_version: &str) { - if args.should_check_for_update() { +fn try_check_for_updates( + args: &ShimArgs, + current_version: &str, + config: &crate::config::ConfigurationOptions, +) { + if args.should_check_for_update() && !config.no_update_notifier() { // custom footer for update message let footer = format!( "Follow {username} for updates: {url}", diff --git a/crates/turborepo-lib/src/shim/parser.rs b/crates/turborepo-lib/src/shim/parser.rs index 15bfaaba330a6..9d88f972406e9 100644 --- a/crates/turborepo-lib/src/shim/parser.rs +++ b/crates/turborepo-lib/src/shim/parser.rs @@ -52,6 +52,7 @@ pub struct ShimArgs { pub forwarded_args: Vec, pub color: bool, pub no_color: bool, + pub root_turbo_json: Option, } impl ShimArgs { @@ -75,6 +76,8 @@ impl ShimArgs { let mut is_forwarded_args = false; let mut color = false; let mut no_color = false; + let mut root_turbo_json_flag_idx = None; + let mut root_turbo_json = None; let args = args.skip(1); for (idx, arg) in args.enumerate() { @@ -127,6 +130,18 @@ impl ShimArgs { color = true; } else if arg == "--no-color" { no_color = true; + } else if root_turbo_json_flag_idx.is_some() { + // We've seen a `--root-turbo-json` and therefore add this as the path + root_turbo_json = Some(AbsoluteSystemPathBuf::from_unknown(&invocation_dir, arg)); + root_turbo_json_flag_idx = None; + } else if arg == "--root-turbo-json" { + // If we see a `--root-turbo-json` we expect the next arg to be a path. + root_turbo_json_flag_idx = Some(idx); + } else if let Some(path) = arg.strip_prefix("--root-turbo-json=") { + // In the case where `--root-turbo-json` is passed as + // `--root-turbo-json=./path/to/turbo.json`, that entire chunk + // is a single arg, so we need to split it up. + root_turbo_json = Some(AbsoluteSystemPathBuf::from_unknown(&invocation_dir, path)); } else { remaining_turbo_args.push(arg); } @@ -143,6 +158,17 @@ impl ShimArgs { }); } + if let Some(idx) = root_turbo_json_flag_idx { + let (spans, args_string) = + Self::get_spans_in_args_string(vec![idx], env::args().skip(1)); + + return Err(Error::EmptyRootTurboJson { + backtrace: Backtrace::capture(), + args_string, + flag_range: spans[0], + }); + } + if cwds.len() > 1 { let (indices, args_string) = Self::get_spans_in_args_string( cwds.iter().map(|(_, idx)| *idx).collect(), @@ -175,6 +201,7 @@ impl ShimArgs { forwarded_args, color, no_color, + root_turbo_json, }) } @@ -301,6 +328,7 @@ mod test { pub color: bool, pub no_color: bool, pub relative_cwd: Option<&'static [&'static str]>, + pub relative_root_turbo_json: Option<&'static str>, } impl ExpectedArgs { @@ -314,6 +342,7 @@ mod test { color, no_color, relative_cwd, + relative_root_turbo_json, } = self; ShimArgs { cwd: relative_cwd.map_or_else( @@ -331,6 +360,8 @@ mod test { force_update_check, color, no_color, + root_turbo_json: relative_root_turbo_json + .map(|path| AbsoluteSystemPathBuf::from_unknown(&invocation_dir, path)), } } } @@ -461,6 +492,30 @@ mod test { } ; "cwd equals" )] + #[test_case( + &["turbo", "--root-turbo-json", "path/to/turbo.json"], + ExpectedArgs { + relative_root_turbo_json: Some("path/to/turbo.json"), + ..Default::default() + } + ; "root turbo json value" + )] + #[test_case( + &["turbo", "--root-turbo-json=path/to/turbo.json"], + ExpectedArgs { + relative_root_turbo_json: Some("path/to/turbo.json"), + ..Default::default() + } + ; "root turbo json equals" + )] + #[test_case( + &["turbo", "--root-turbo-json", "/absolute/path/to/turbo.json"], + ExpectedArgs { + relative_root_turbo_json: Some("/absolute/path/to/turbo.json"), + ..Default::default() + } + ; "root turbo json absolute path" + )] fn test_shim_parsing(args: &[&str], expected: ExpectedArgs) { let cwd = AbsoluteSystemPathBuf::new(if cfg!(windows) { "Z:\\some\\dir" diff --git a/crates/turborepo-lib/src/turbo_json/mod.rs b/crates/turborepo-lib/src/turbo_json/mod.rs index d9e4e33d0a21c..005934e2cc61f 100644 --- a/crates/turborepo-lib/src/turbo_json/mod.rs +++ b/crates/turborepo-lib/src/turbo_json/mod.rs @@ -152,6 +152,9 @@ pub struct RawTurboJson { #[serde(skip_serializing_if = "Option::is_none")] pub cache_dir: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub no_update_notifier: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub tags: Option>>>, diff --git a/crates/turborepo-updater/src/lib.rs b/crates/turborepo-updater/src/lib.rs index 6276b0d547fa5..09c4b4cbd7fad 100644 --- a/crates/turborepo-updater/src/lib.rs +++ b/crates/turborepo-updater/src/lib.rs @@ -21,7 +21,7 @@ const DEFAULT_TIMEOUT: Duration = Duration::from_millis(800); // 1 day const DEFAULT_INTERVAL: Duration = Duration::from_secs(60 * 60 * 24); -const NOTIFIER_DISABLE_VARS: [&str; 2] = ["NO_UPDATE_NOTIFIER", "TURBO_NO_UPDATE_NOTIFIER"]; +const NOTIFIER_DISABLE_VARS: [&str; 1] = ["NO_UPDATE_NOTIFIER"]; const ENVIRONMENTAL_DISABLE_VARS: [&str; 1] = ["CI"]; #[derive(ThisError, Debug)] diff --git a/docs/site/content/docs/reference/configuration.mdx b/docs/site/content/docs/reference/configuration.mdx index cc0d2f49a99a3..20a055259e640 100644 --- a/docs/site/content/docs/reference/configuration.mdx +++ b/docs/site/content/docs/reference/configuration.mdx @@ -91,6 +91,18 @@ Select a terminal UI for the repository. } ``` +### `noUpdateNotifier` + +Default: `false` + +When set to `true`, disables the update notification that appears when a new version of `turbo` is available. + +```json title="./turbo.json" +{ + "noUpdateNotifier": true +} +``` + ### `concurrency` Default: `"10"` diff --git a/packages/turbo-types/schemas/schema.json b/packages/turbo-types/schemas/schema.json index 035fa5300c285..8f164b2a8b802 100644 --- a/packages/turbo-types/schemas/schema.json +++ b/packages/turbo-types/schemas/schema.json @@ -97,6 +97,11 @@ "boundaries": { "$ref": "#/definitions/RootBoundariesConfig", "description": "Configuration for `turbo boundaries`. Allows users to restrict a package's dependencies and dependents" + }, + "noUpdateNotifier": { + "type": "boolean", + "description": "When set to `true`, disables the update notification that appears when a new version of `turbo` is available.\n\nDocumentation: https://turborepo.com/docs/reference/configuration#noupdatenotifier", + "default": false } }, "additionalProperties": false, diff --git a/packages/turbo-types/schemas/schema.v2.json b/packages/turbo-types/schemas/schema.v2.json index 035fa5300c285..8f164b2a8b802 100644 --- a/packages/turbo-types/schemas/schema.v2.json +++ b/packages/turbo-types/schemas/schema.v2.json @@ -97,6 +97,11 @@ "boundaries": { "$ref": "#/definitions/RootBoundariesConfig", "description": "Configuration for `turbo boundaries`. Allows users to restrict a package's dependencies and dependents" + }, + "noUpdateNotifier": { + "type": "boolean", + "description": "When set to `true`, disables the update notification that appears when a new version of `turbo` is available.\n\nDocumentation: https://turborepo.com/docs/reference/configuration#noupdatenotifier", + "default": false } }, "additionalProperties": false, diff --git a/packages/turbo-types/src/types/config-v2.ts b/packages/turbo-types/src/types/config-v2.ts index c435da9fce22c..54a91a68f6fb6 100644 --- a/packages/turbo-types/src/types/config-v2.ts +++ b/packages/turbo-types/src/types/config-v2.ts @@ -181,6 +181,15 @@ export interface RootSchema extends BaseSchema { * Configuration for `turbo boundaries`. Allows users to restrict a package's dependencies and dependents */ boundaries?: RootBoundariesConfig; + + /** + * When set to `true`, disables the update notification that appears when a new version of `turbo` is available. + * + * Documentation: https://turborepo.com/docs/reference/configuration#noupdatenotifier + * + * @defaultValue `false` + */ + noUpdateNotifier?: boolean; } export interface Pipeline {